7c77f2e97a4c85c15154b4cb6757428391e95eb1
[project/luci.git] / libs / web / luasrc / dispatcher.lua
1 --[[
2 LuCI - Dispatcher
3
4 Description:
5 The request dispatcher and module dispatcher generators
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
16
17 http://www.apache.org/licenses/LICENSE-2.0
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26
27 --- LuCI web dispatcher.
28 local fs = require "nixio.fs"
29 local sys = require "luci.sys"
30 local init = require "luci.init"
31 local util = require "luci.util"
32 local http = require "luci.http"
33 local nixio = require "nixio", require "nixio.util"
34
35 module("luci.dispatcher", package.seeall)
36 context = util.threadlocal()
37 uci = require "luci.model.uci"
38 i18n = require "luci.i18n"
39 _M.fs = fs
40
41 authenticator = {}
42
43 -- Index table
44 local index = nil
45
46 -- Fastindex
47 local fi
48
49
50 --- Build the URL relative to the server webroot from given virtual path.
51 -- @param ... Virtual path
52 -- @return Relative URL
53 function build_url(...)
54 local path = {...}
55 local url = { http.getenv("SCRIPT_NAME") or "" }
56
57 local k, v
58 for k, v in pairs(context.urltoken) do
59 url[#url+1] = "/;"
60 url[#url+1] = http.urlencode(k)
61 url[#url+1] = "="
62 url[#url+1] = http.urlencode(v)
63 end
64
65 local p
66 for _, p in ipairs(path) do
67 if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then
68 url[#url+1] = "/"
69 url[#url+1] = p
70 end
71 end
72
73 return table.concat(url, "")
74 end
75
76 --- Check whether a dispatch node shall be visible
77 -- @param node Dispatch node
78 -- @return Boolean indicating whether the node should be visible
79 function node_visible(node)
80 if node then
81 return not (
82 (not node.title or #node.title == 0) or
83 (not node.target or node.hidden == true) or
84 (type(node.target) == "table" and node.target.type == "firstchild" and
85 (type(node.nodes) ~= "table" or not next(node.nodes)))
86 )
87 end
88 return false
89 end
90
91 --- Return a sorted table of visible childs within a given node
92 -- @param node Dispatch node
93 -- @return Ordered table of child node names
94 function node_childs(node)
95 local rv = { }
96 if node then
97 local k, v
98 for k, v in util.spairs(node.nodes,
99 function(a, b)
100 return (node.nodes[a].order or 100)
101 < (node.nodes[b].order or 100)
102 end)
103 do
104 if node_visible(v) then
105 rv[#rv+1] = k
106 end
107 end
108 end
109 return rv
110 end
111
112
113 --- Send a 404 error code and render the "error404" template if available.
114 -- @param message Custom error message (optional)
115 -- @return false
116 function error404(message)
117 luci.http.status(404, "Not Found")
118 message = message or "Not Found"
119
120 require("luci.template")
121 if not luci.util.copcall(luci.template.render, "error404") then
122 luci.http.prepare_content("text/plain")
123 luci.http.write(message)
124 end
125 return false
126 end
127
128 --- Send a 500 error code and render the "error500" template if available.
129 -- @param message Custom error message (optional)#
130 -- @return false
131 function error500(message)
132 luci.util.perror(message)
133 if not context.template_header_sent then
134 luci.http.status(500, "Internal Server Error")
135 luci.http.prepare_content("text/plain")
136 luci.http.write(message)
137 else
138 require("luci.template")
139 if not luci.util.copcall(luci.template.render, "error500", {message=message}) then
140 luci.http.prepare_content("text/plain")
141 luci.http.write(message)
142 end
143 end
144 return false
145 end
146
147 function authenticator.htmlauth(validator, accs, default)
148 local user = luci.http.formvalue("username")
149 local pass = luci.http.formvalue("password")
150
151 if user and validator(user, pass) then
152 return user
153 end
154
155 require("luci.i18n")
156 require("luci.template")
157 context.path = {}
158 luci.template.render("sysauth", {duser=default, fuser=user})
159 return false
160
161 end
162
163 --- Dispatch an HTTP request.
164 -- @param request LuCI HTTP Request object
165 function httpdispatch(request, prefix)
166 luci.http.context.request = request
167
168 local r = {}
169 context.request = r
170 context.urltoken = {}
171
172 local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
173
174 if prefix then
175 for _, node in ipairs(prefix) do
176 r[#r+1] = node
177 end
178 end
179
180 local tokensok = true
181 for node in pathinfo:gmatch("[^/]+") do
182 local tkey, tval
183 if tokensok then
184 tkey, tval = node:match(";(%w+)=([a-fA-F0-9]*)")
185 end
186 if tkey then
187 context.urltoken[tkey] = tval
188 else
189 tokensok = false
190 r[#r+1] = node
191 end
192 end
193
194 local stat, err = util.coxpcall(function()
195 dispatch(context.request)
196 end, error500)
197
198 luci.http.close()
199
200 --context._disable_memtrace()
201 end
202
203 --- Dispatches a LuCI virtual path.
204 -- @param request Virtual path
205 function dispatch(request)
206 --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
207 local ctx = context
208 ctx.path = request
209
210 local conf = require "luci.config"
211 assert(conf.main,
212 "/etc/config/luci seems to be corrupt, unable to find section 'main'")
213
214 local lang = conf.main.lang or "auto"
215 if lang == "auto" then
216 local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
217 for lpat in aclang:gmatch("[%w-]+") do
218 lpat = lpat and lpat:gsub("-", "_")
219 if conf.languages[lpat] then
220 lang = lpat
221 break
222 end
223 end
224 end
225 require "luci.i18n".setlanguage(lang)
226
227 local c = ctx.tree
228 local stat
229 if not c then
230 c = createtree()
231 end
232
233 local track = {}
234 local args = {}
235 ctx.args = args
236 ctx.requestargs = ctx.requestargs or args
237 local n
238 local token = ctx.urltoken
239 local preq = {}
240 local freq = {}
241
242 for i, s in ipairs(request) do
243 preq[#preq+1] = s
244 freq[#freq+1] = s
245 c = c.nodes[s]
246 n = i
247 if not c then
248 break
249 end
250
251 util.update(track, c)
252
253 if c.leaf then
254 break
255 end
256 end
257
258 if c and c.leaf then
259 for j=n+1, #request do
260 args[#args+1] = request[j]
261 freq[#freq+1] = request[j]
262 end
263 end
264
265 ctx.requestpath = ctx.requestpath or freq
266 ctx.path = preq
267
268 if track.i18n then
269 i18n.loadc(track.i18n)
270 end
271
272 -- Init template engine
273 if (c and c.index) or not track.notemplate then
274 local tpl = require("luci.template")
275 local media = track.mediaurlbase or luci.config.main.mediaurlbase
276 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
277 media = nil
278 for name, theme in pairs(luci.config.themes) do
279 if name:sub(1,1) ~= "." and pcall(tpl.Template,
280 "themes/%s/header" % fs.basename(theme)) then
281 media = theme
282 end
283 end
284 assert(media, "No valid theme found")
285 end
286
287 local function _ifattr(cond, key, val)
288 if cond then
289 local env = getfenv(3)
290 local scope = (type(env.self) == "table") and env.self
291 return string.format(
292 ' %s="%s"', tostring(key),
293 luci.util.pcdata(tostring( val
294 or (type(env[key]) ~= "function" and env[key])
295 or (scope and type(scope[key]) ~= "function" and scope[key])
296 or "" ))
297 )
298 else
299 return ''
300 end
301 end
302
303 tpl.context.viewns = setmetatable({
304 write = luci.http.write;
305 include = function(name) tpl.Template(name):render(getfenv(2)) end;
306 translate = i18n.translate;
307 export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
308 striptags = util.striptags;
309 pcdata = util.pcdata;
310 media = media;
311 theme = fs.basename(media);
312 resource = luci.config.main.resourcebase;
313 ifattr = function(...) return _ifattr(...) end;
314 attr = function(...) return _ifattr(true, ...) end;
315 }, {__index=function(table, key)
316 if key == "controller" then
317 return build_url()
318 elseif key == "REQUEST_URI" then
319 return build_url(unpack(ctx.requestpath))
320 else
321 return rawget(table, key) or _G[key]
322 end
323 end})
324 end
325
326 track.dependent = (track.dependent ~= false)
327 assert(not track.dependent or not track.auto,
328 "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " ..
329 "has no parent node so the access to this location has been denied.\n" ..
330 "This is a software bug, please report this message at " ..
331 "http://luci.subsignal.org/trac/newticket"
332 )
333
334 if track.sysauth then
335 local sauth = require "luci.sauth"
336
337 local authen = type(track.sysauth_authenticator) == "function"
338 and track.sysauth_authenticator
339 or authenticator[track.sysauth_authenticator]
340
341 local def = (type(track.sysauth) == "string") and track.sysauth
342 local accs = def and {track.sysauth} or track.sysauth
343 local sess = ctx.authsession
344 local verifytoken = false
345 if not sess then
346 sess = luci.http.getcookie("sysauth")
347 sess = sess and sess:match("^[a-f0-9]*$")
348 verifytoken = true
349 end
350
351 local sdat = sauth.read(sess)
352 local user
353
354 if sdat then
355 sdat = loadstring(sdat)
356 setfenv(sdat, {})
357 sdat = sdat()
358 if not verifytoken or ctx.urltoken.stok == sdat.token then
359 user = sdat.user
360 end
361 else
362 local eu = http.getenv("HTTP_AUTH_USER")
363 local ep = http.getenv("HTTP_AUTH_PASS")
364 if eu and ep and luci.sys.user.checkpasswd(eu, ep) then
365 authen = function() return eu end
366 end
367 end
368
369 if not util.contains(accs, user) then
370 if authen then
371 ctx.urltoken.stok = nil
372 local user, sess = authen(luci.sys.user.checkpasswd, accs, def)
373 if not user or not util.contains(accs, user) then
374 return
375 else
376 local sid = sess or luci.sys.uniqueid(16)
377 if not sess then
378 local token = luci.sys.uniqueid(16)
379 sauth.write(sid, util.get_bytecode({
380 user=user,
381 token=token,
382 secret=luci.sys.uniqueid(16)
383 }))
384 ctx.urltoken.stok = token
385 end
386 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path="..build_url())
387 ctx.authsession = sid
388 ctx.authuser = user
389 end
390 else
391 luci.http.status(403, "Forbidden")
392 return
393 end
394 else
395 ctx.authsession = sess
396 ctx.authuser = user
397 end
398 end
399
400 if track.setgroup then
401 luci.sys.process.setgroup(track.setgroup)
402 end
403
404 if track.setuser then
405 luci.sys.process.setuser(track.setuser)
406 end
407
408 local target = nil
409 if c then
410 if type(c.target) == "function" then
411 target = c.target
412 elseif type(c.target) == "table" then
413 target = c.target.target
414 end
415 end
416
417 if c and (c.index or type(target) == "function") then
418 ctx.dispatched = c
419 ctx.requested = ctx.requested or ctx.dispatched
420 end
421
422 if c and c.index then
423 local tpl = require "luci.template"
424
425 if util.copcall(tpl.render, "indexer", {}) then
426 return true
427 end
428 end
429
430 if type(target) == "function" then
431 util.copcall(function()
432 local oldenv = getfenv(target)
433 local module = require(c.module)
434 local env = setmetatable({}, {__index=
435
436 function(tbl, key)
437 return rawget(tbl, key) or module[key] or oldenv[key]
438 end})
439
440 setfenv(target, env)
441 end)
442
443 local ok, err
444 if type(c.target) == "table" then
445 ok, err = util.copcall(target, c.target, unpack(args))
446 else
447 ok, err = util.copcall(target, unpack(args))
448 end
449 assert(ok,
450 "Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
451 " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
452 "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
453 else
454 local root = node()
455 if not root or not root.target then
456 error404("No root node was registered, this usually happens if no module was installed.\n" ..
457 "Install luci-mod-admin-full and retry. " ..
458 "If the module is already installed, try removing the /tmp/luci-indexcache file.")
459 else
460 error404("No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
461 "If this url belongs to an extension, make sure it is properly installed.\n" ..
462 "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
463 end
464 end
465 end
466
467 --- Generate the dispatching index using the best possible strategy.
468 function createindex()
469 local path = luci.util.libpath() .. "/controller/"
470 local suff = { ".lua", ".lua.gz" }
471
472 if luci.util.copcall(require, "luci.fastindex") then
473 createindex_fastindex(path, suff)
474 else
475 createindex_plain(path, suff)
476 end
477 end
478
479 --- Generate the dispatching index using the fastindex C-indexer.
480 -- @param path Controller base directory
481 -- @param suffixes Controller file suffixes
482 function createindex_fastindex(path, suffixes)
483 index = {}
484
485 if not fi then
486 fi = luci.fastindex.new("index")
487 for _, suffix in ipairs(suffixes) do
488 fi.add(path .. "*" .. suffix)
489 fi.add(path .. "*/*" .. suffix)
490 end
491 end
492 fi.scan()
493
494 for k, v in pairs(fi.indexes) do
495 index[v[2]] = v[1]
496 end
497 end
498
499 --- Generate the dispatching index using the native file-cache based strategy.
500 -- @param path Controller base directory
501 -- @param suffixes Controller file suffixes
502 function createindex_plain(path, suffixes)
503 local controllers = { }
504 for _, suffix in ipairs(suffixes) do
505 nixio.util.consume((fs.glob(path .. "*" .. suffix)), controllers)
506 nixio.util.consume((fs.glob(path .. "*/*" .. suffix)), controllers)
507 end
508
509 if indexcache then
510 local cachedate = fs.stat(indexcache, "mtime")
511 if cachedate then
512 local realdate = 0
513 for _, obj in ipairs(controllers) do
514 local omtime = fs.stat(obj, "mtime")
515 realdate = (omtime and omtime > realdate) and omtime or realdate
516 end
517
518 if cachedate > realdate then
519 assert(
520 sys.process.info("uid") == fs.stat(indexcache, "uid")
521 and fs.stat(indexcache, "modestr") == "rw-------",
522 "Fatal: Indexcache is not sane!"
523 )
524
525 index = loadfile(indexcache)()
526 return index
527 end
528 end
529 end
530
531 index = {}
532
533 for i,c in ipairs(controllers) do
534 local modname = "luci.controller." .. c:sub(#path+1, #c):gsub("/", ".")
535 for _, suffix in ipairs(suffixes) do
536 modname = modname:gsub(suffix.."$", "")
537 end
538
539 local mod = require(modname)
540 assert(mod ~= true,
541 "Invalid controller file found\n" ..
542 "The file '" .. c .. "' contains an invalid module line.\n" ..
543 "Please verify whether the module name is set to '" .. modname ..
544 "' - It must correspond to the file path!")
545
546 local idx = mod.index
547 assert(type(idx) == "function",
548 "Invalid controller file found\n" ..
549 "The file '" .. c .. "' contains no index() function.\n" ..
550 "Please make sure that the controller contains a valid " ..
551 "index function and verify the spelling!")
552
553 index[modname] = idx
554 end
555
556 if indexcache then
557 local f = nixio.open(indexcache, "w", 600)
558 f:writeall(util.get_bytecode(index))
559 f:close()
560 end
561 end
562
563 --- Create the dispatching tree from the index.
564 -- Build the index before if it does not exist yet.
565 function createtree()
566 if not index then
567 createindex()
568 end
569
570 local ctx = context
571 local tree = {nodes={}, inreq=true}
572 local modi = {}
573
574 ctx.treecache = setmetatable({}, {__mode="v"})
575 ctx.tree = tree
576 ctx.modifiers = modi
577
578 -- Load default translation
579 require "luci.i18n".loadc("base")
580
581 local scope = setmetatable({}, {__index = luci.dispatcher})
582
583 for k, v in pairs(index) do
584 scope._NAME = k
585 setfenv(v, scope)
586 v()
587 end
588
589 local function modisort(a,b)
590 return modi[a].order < modi[b].order
591 end
592
593 for _, v in util.spairs(modi, modisort) do
594 scope._NAME = v.module
595 setfenv(v.func, scope)
596 v.func()
597 end
598
599 return tree
600 end
601
602 --- Register a tree modifier.
603 -- @param func Modifier function
604 -- @param order Modifier order value (optional)
605 function modifier(func, order)
606 context.modifiers[#context.modifiers+1] = {
607 func = func,
608 order = order or 0,
609 module
610 = getfenv(2)._NAME
611 }
612 end
613
614 --- Clone a node of the dispatching tree to another position.
615 -- @param path Virtual path destination
616 -- @param clone Virtual path source
617 -- @param title Destination node title (optional)
618 -- @param order Destination node order value (optional)
619 -- @return Dispatching tree node
620 function assign(path, clone, title, order)
621 local obj = node(unpack(path))
622 obj.nodes = nil
623 obj.module = nil
624
625 obj.title = title
626 obj.order = order
627
628 setmetatable(obj, {__index = _create_node(clone)})
629
630 return obj
631 end
632
633 --- Create a new dispatching node and define common parameters.
634 -- @param path Virtual path
635 -- @param target Target function to call when dispatched.
636 -- @param title Destination node title
637 -- @param order Destination node order value (optional)
638 -- @return Dispatching tree node
639 function entry(path, target, title, order)
640 local c = node(unpack(path))
641
642 c.target = target
643 c.title = title
644 c.order = order
645 c.module = getfenv(2)._NAME
646
647 return c
648 end
649
650 --- Fetch or create a dispatching node without setting the target module or
651 -- enabling the node.
652 -- @param ... Virtual path
653 -- @return Dispatching tree node
654 function get(...)
655 return _create_node({...})
656 end
657
658 --- Fetch or create a new dispatching node.
659 -- @param ... Virtual path
660 -- @return Dispatching tree node
661 function node(...)
662 local c = _create_node({...})
663
664 c.module = getfenv(2)._NAME
665 c.auto = nil
666
667 return c
668 end
669
670 function _create_node(path)
671 if #path == 0 then
672 return context.tree
673 end
674
675 local name = table.concat(path, ".")
676 local c = context.treecache[name]
677
678 if not c then
679 local last = table.remove(path)
680 local parent = _create_node(path)
681
682 c = {nodes={}, auto=true}
683 -- the node is "in request" if the request path matches
684 -- at least up to the length of the node path
685 if parent.inreq and context.path[#path+1] == last then
686 c.inreq = true
687 end
688 parent.nodes[last] = c
689 context.treecache[name] = c
690 end
691 return c
692 end
693
694 -- Subdispatchers --
695
696 function _firstchild()
697 local path = { unpack(context.path) }
698 local name = table.concat(path, ".")
699 local node = context.treecache[name]
700
701 local lowest
702 if node and node.nodes and next(node.nodes) then
703 local k, v
704 for k, v in pairs(node.nodes) do
705 if not lowest or
706 (v.order or 100) < (node.nodes[lowest].order or 100)
707 then
708 lowest = k
709 end
710 end
711 end
712
713 assert(lowest ~= nil,
714 "The requested node contains no childs, unable to redispatch")
715
716 path[#path+1] = lowest
717 dispatch(path)
718 end
719
720 --- Alias the first (lowest order) page automatically
721 function firstchild()
722 return { type = "firstchild", target = _firstchild }
723 end
724
725 --- Create a redirect to another dispatching node.
726 -- @param ... Virtual path destination
727 function alias(...)
728 local req = {...}
729 return function(...)
730 for _, r in ipairs({...}) do
731 req[#req+1] = r
732 end
733
734 dispatch(req)
735 end
736 end
737
738 --- Rewrite the first x path values of the request.
739 -- @param n Number of path values to replace
740 -- @param ... Virtual path to replace removed path values with
741 function rewrite(n, ...)
742 local req = {...}
743 return function(...)
744 local dispatched = util.clone(context.dispatched)
745
746 for i=1,n do
747 table.remove(dispatched, 1)
748 end
749
750 for i, r in ipairs(req) do
751 table.insert(dispatched, i, r)
752 end
753
754 for _, r in ipairs({...}) do
755 dispatched[#dispatched+1] = r
756 end
757
758 dispatch(dispatched)
759 end
760 end
761
762
763 local function _call(self, ...)
764 local func = getfenv()[self.name]
765 assert(func ~= nil,
766 'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?')
767
768 assert(type(func) == "function",
769 'The symbol "' .. self.name .. '" does not refer to a function but data ' ..
770 'of type "' .. type(func) .. '".')
771
772 if #self.argv > 0 then
773 return func(unpack(self.argv), ...)
774 else
775 return func(...)
776 end
777 end
778
779 --- Create a function-call dispatching target.
780 -- @param name Target function of local controller
781 -- @param ... Additional parameters passed to the function
782 function call(name, ...)
783 return {type = "call", argv = {...}, name = name, target = _call}
784 end
785
786
787 local _template = function(self, ...)
788 require "luci.template".render(self.view)
789 end
790
791 --- Create a template render dispatching target.
792 -- @param name Template to be rendered
793 function template(name)
794 return {type = "template", view = name, target = _template}
795 end
796
797
798 local function _cbi(self, ...)
799 local cbi = require "luci.cbi"
800 local tpl = require "luci.template"
801 local http = require "luci.http"
802
803 local config = self.config or {}
804 local maps = cbi.load(self.model, ...)
805
806 local state = nil
807
808 for i, res in ipairs(maps) do
809 res.flow = config
810 local cstate = res:parse()
811 if cstate and (not state or cstate < state) then
812 state = cstate
813 end
814 end
815
816 local function _resolve_path(path)
817 return type(path) == "table" and build_url(unpack(path)) or path
818 end
819
820 if config.on_valid_to and state and state > 0 and state < 2 then
821 http.redirect(_resolve_path(config.on_valid_to))
822 return
823 end
824
825 if config.on_changed_to and state and state > 1 then
826 http.redirect(_resolve_path(config.on_changed_to))
827 return
828 end
829
830 if config.on_success_to and state and state > 0 then
831 http.redirect(_resolve_path(config.on_success_to))
832 return
833 end
834
835 if config.state_handler then
836 if not config.state_handler(state, maps) then
837 return
838 end
839 end
840
841 http.header("X-CBI-State", state or 0)
842
843 if not config.noheader then
844 tpl.render("cbi/header", {state = state})
845 end
846
847 local redirect
848 local messages
849 local applymap = false
850 local pageaction = true
851 local parsechain = { }
852
853 for i, res in ipairs(maps) do
854 if res.apply_needed and res.parsechain then
855 local c
856 for _, c in ipairs(res.parsechain) do
857 parsechain[#parsechain+1] = c
858 end
859 applymap = true
860 end
861
862 if res.redirect then
863 redirect = redirect or res.redirect
864 end
865
866 if res.pageaction == false then
867 pageaction = false
868 end
869
870 if res.message then
871 messages = messages or { }
872 messages[#messages+1] = res.message
873 end
874 end
875
876 for i, res in ipairs(maps) do
877 res:render({
878 firstmap = (i == 1),
879 applymap = applymap,
880 redirect = redirect,
881 messages = messages,
882 pageaction = pageaction,
883 parsechain = parsechain
884 })
885 end
886
887 if not config.nofooter then
888 tpl.render("cbi/footer", {
889 flow = config,
890 pageaction = pageaction,
891 redirect = redirect,
892 state = state,
893 autoapply = config.autoapply
894 })
895 end
896 end
897
898 --- Create a CBI model dispatching target.
899 -- @param model CBI model to be rendered
900 function cbi(model, config)
901 return {type = "cbi", config = config, model = model, target = _cbi}
902 end
903
904
905 local function _arcombine(self, ...)
906 local argv = {...}
907 local target = #argv > 0 and self.targets[2] or self.targets[1]
908 setfenv(target.target, self.env)
909 target:target(unpack(argv))
910 end
911
912 --- Create a combined dispatching target for non argv and argv requests.
913 -- @param trg1 Overview Target
914 -- @param trg2 Detail Target
915 function arcombine(trg1, trg2)
916 return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
917 end
918
919
920 local function _form(self, ...)
921 local cbi = require "luci.cbi"
922 local tpl = require "luci.template"
923 local http = require "luci.http"
924
925 local maps = luci.cbi.load(self.model, ...)
926 local state = nil
927
928 for i, res in ipairs(maps) do
929 local cstate = res:parse()
930 if cstate and (not state or cstate < state) then
931 state = cstate
932 end
933 end
934
935 http.header("X-CBI-State", state or 0)
936 tpl.render("header")
937 for i, res in ipairs(maps) do
938 res:render()
939 end
940 tpl.render("footer")
941 end
942
943 --- Create a CBI form model dispatching target.
944 -- @param model CBI form model tpo be rendered
945 function form(model)
946 return {type = "cbi", model = model, target = _form}
947 end
948
949 --- Access the luci.i18n translate() api.
950 -- @class function
951 -- @name translate
952 -- @param text Text to translate
953 translate = i18n.translate
954
955 --- No-op function used to mark translation entries for menu labels.
956 -- This function does not actually translate the given argument but
957 -- is used by build/i18n-scan.pl to find translatable entries.
958 function _(text)
959 return text
960 end