dispatcher.lua 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  1. -- Copyright 2008 Steven Barth <steven@midlink.org>
  2. -- Licensed to the public under the Apache License 2.0.
  3. local fs = require "nixio.fs"
  4. local sys = require "luci.sys"
  5. local util = require "luci.util"
  6. local http = require "luci.http"
  7. local nixio = require "nixio", require "nixio.util"
  8. module("luci.dispatcher", package.seeall)
  9. context = util.threadlocal()
  10. uci = require "luci.model.uci"
  11. i18n = require "luci.i18n"
  12. _M.fs = fs
  13. authenticator = {}
  14. -- Index table
  15. local index = nil
  16. -- Fastindex
  17. local fi
  18. function build_url(...)
  19. local path = {...}
  20. local url = { http.getenv("SCRIPT_NAME") or "" }
  21. local k, v
  22. for k, v in pairs(context.urltoken) do
  23. url[#url+1] = "/;"
  24. url[#url+1] = http.urlencode(k)
  25. url[#url+1] = "="
  26. url[#url+1] = http.urlencode(v)
  27. end
  28. local p
  29. for _, p in ipairs(path) do
  30. if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then
  31. url[#url+1] = "/"
  32. url[#url+1] = p
  33. end
  34. end
  35. return table.concat(url, "")
  36. end
  37. function node_visible(node)
  38. if node then
  39. return not (
  40. (not node.title or #node.title == 0) or
  41. (not node.target or node.hidden == true) or
  42. (type(node.target) == "table" and node.target.type == "firstchild" and
  43. (type(node.nodes) ~= "table" or not next(node.nodes)))
  44. )
  45. end
  46. return false
  47. end
  48. function node_childs(node)
  49. local rv = { }
  50. if node then
  51. local k, v
  52. for k, v in util.spairs(node.nodes,
  53. function(a, b)
  54. return (node.nodes[a].order or 100)
  55. < (node.nodes[b].order or 100)
  56. end)
  57. do
  58. if node_visible(v) then
  59. rv[#rv+1] = k
  60. end
  61. end
  62. end
  63. return rv
  64. end
  65. function error404(message)
  66. http.status(404, "Not Found")
  67. message = message or "Not Found"
  68. require("luci.template")
  69. if not util.copcall(luci.template.render, "error404") then
  70. http.prepare_content("text/plain")
  71. http.write(message)
  72. end
  73. return false
  74. end
  75. function error500(message)
  76. util.perror(message)
  77. if not context.template_header_sent then
  78. http.status(500, "Internal Server Error")
  79. http.prepare_content("text/plain")
  80. http.write(message)
  81. else
  82. require("luci.template")
  83. if not util.copcall(luci.template.render, "error500", {message=message}) then
  84. http.prepare_content("text/plain")
  85. http.write(message)
  86. end
  87. end
  88. return false
  89. end
  90. function authenticator.avalonauth(validator, accs, default)
  91. local user = luci.http.formvalue("username")
  92. local pass = luci.http.formvalue("password")
  93. if user and validator(user, pass) then
  94. return user
  95. end
  96. require("luci.i18n")
  97. require("luci.template")
  98. context.path = {}
  99. luci.template.render("avalonauth", {duser=default, fuser=user})
  100. return false
  101. end
  102. function authenticator.htmlauth(validator, accs, default)
  103. local user = http.formvalue("luci_username")
  104. local pass = http.formvalue("luci_password")
  105. if user and validator(user, pass) then
  106. return user
  107. end
  108. if context.urltoken.stok then
  109. context.urltoken.stok = nil
  110. local cookie = 'sysauth=%s; expires=%s; path=%s/' %{
  111. http.getcookie('sysauth') or 'x',
  112. 'Thu, 01 Jan 1970 01:00:00 GMT',
  113. build_url()
  114. }
  115. http.header("Set-Cookie", cookie)
  116. http.redirect(build_url())
  117. else
  118. require("luci.i18n")
  119. require("luci.template")
  120. context.path = {}
  121. http.status(403, "Forbidden")
  122. luci.template.render("sysauth", {duser=default, fuser=user})
  123. end
  124. return false
  125. end
  126. function httpdispatch(request, prefix)
  127. http.context.request = request
  128. local r = {}
  129. context.request = r
  130. context.urltoken = {}
  131. local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
  132. if prefix then
  133. for _, node in ipairs(prefix) do
  134. r[#r+1] = node
  135. end
  136. end
  137. local tokensok = true
  138. for node in pathinfo:gmatch("[^/]+") do
  139. local tkey, tval
  140. if tokensok then
  141. tkey, tval = node:match(";(%w+)=([a-fA-F0-9]*)")
  142. end
  143. if tkey then
  144. context.urltoken[tkey] = tval
  145. else
  146. tokensok = false
  147. r[#r+1] = node
  148. end
  149. end
  150. local stat, err = util.coxpcall(function()
  151. dispatch(context.request)
  152. end, error500)
  153. http.close()
  154. --context._disable_memtrace()
  155. end
  156. function dispatch(request)
  157. --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
  158. local ctx = context
  159. ctx.path = request
  160. local conf = require "luci.config"
  161. assert(conf.main,
  162. "/etc/config/luci seems to be corrupt, unable to find section 'main'")
  163. local lang = conf.main.lang or "auto"
  164. if lang == "auto" then
  165. local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
  166. for lpat in aclang:gmatch("[%w-]+") do
  167. lpat = lpat and lpat:gsub("-", "_")
  168. if conf.languages[lpat] then
  169. lang = lpat
  170. break
  171. end
  172. end
  173. end
  174. require "luci.i18n".setlanguage(lang)
  175. local c = ctx.tree
  176. local stat
  177. if not c then
  178. c = createtree()
  179. end
  180. local track = {}
  181. local args = {}
  182. ctx.args = args
  183. ctx.requestargs = ctx.requestargs or args
  184. local n
  185. local token = ctx.urltoken
  186. local preq = {}
  187. local freq = {}
  188. for i, s in ipairs(request) do
  189. preq[#preq+1] = s
  190. freq[#freq+1] = s
  191. c = c.nodes[s]
  192. n = i
  193. if not c then
  194. break
  195. end
  196. util.update(track, c)
  197. if c.leaf then
  198. break
  199. end
  200. end
  201. if c and c.leaf then
  202. for j=n+1, #request do
  203. args[#args+1] = request[j]
  204. freq[#freq+1] = request[j]
  205. end
  206. end
  207. ctx.requestpath = ctx.requestpath or freq
  208. ctx.path = preq
  209. if track.i18n then
  210. i18n.loadc(track.i18n)
  211. end
  212. -- Init template engine
  213. if (c and c.index) or not track.notemplate then
  214. local tpl = require("luci.template")
  215. local media = track.mediaurlbase or luci.config.main.mediaurlbase
  216. if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
  217. media = nil
  218. for name, theme in pairs(luci.config.themes) do
  219. if name:sub(1,1) ~= "." and pcall(tpl.Template,
  220. "themes/%s/header" % fs.basename(theme)) then
  221. media = theme
  222. end
  223. end
  224. assert(media, "No valid theme found")
  225. end
  226. local function _ifattr(cond, key, val)
  227. if cond then
  228. local env = getfenv(3)
  229. local scope = (type(env.self) == "table") and env.self
  230. return string.format(
  231. ' %s="%s"', tostring(key),
  232. util.pcdata(tostring( val
  233. or (type(env[key]) ~= "function" and env[key])
  234. or (scope and type(scope[key]) ~= "function" and scope[key])
  235. or "" ))
  236. )
  237. else
  238. return ''
  239. end
  240. end
  241. tpl.context.viewns = setmetatable({
  242. write = http.write;
  243. include = function(name) tpl.Template(name):render(getfenv(2)) end;
  244. translate = i18n.translate;
  245. translatef = i18n.translatef;
  246. export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
  247. striptags = util.striptags;
  248. pcdata = util.pcdata;
  249. media = media;
  250. theme = fs.basename(media);
  251. resource = luci.config.main.resourcebase;
  252. ifattr = function(...) return _ifattr(...) end;
  253. attr = function(...) return _ifattr(true, ...) end;
  254. }, {__index=function(table, key)
  255. if key == "controller" then
  256. return build_url()
  257. elseif key == "REQUEST_URI" then
  258. return build_url(unpack(ctx.requestpath))
  259. else
  260. return rawget(table, key) or _G[key]
  261. end
  262. end})
  263. end
  264. track.dependent = (track.dependent ~= false)
  265. assert(not track.dependent or not track.auto,
  266. "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " ..
  267. "has no parent node so the access to this location has been denied.\n" ..
  268. "This is a software bug, please report this message at " ..
  269. "http://luci.subsignal.org/trac/newticket"
  270. )
  271. if track.sysauth then
  272. local authen = type(track.sysauth_authenticator) == "function"
  273. and track.sysauth_authenticator
  274. or authenticator[track.sysauth_authenticator]
  275. local def = (type(track.sysauth) == "string") and track.sysauth
  276. local accs = def and {track.sysauth} or track.sysauth
  277. local sess = ctx.authsession
  278. local verifytoken = false
  279. if not sess then
  280. sess = http.getcookie("sysauth")
  281. sess = sess and sess:match("^[a-f0-9]*$")
  282. verifytoken = true
  283. end
  284. local sdat = (util.ubus("session", "get", { ubus_rpc_session = sess }) or { }).values
  285. local user
  286. if sdat then
  287. if not verifytoken or ctx.urltoken.stok == sdat.token then
  288. user = sdat.user
  289. end
  290. else
  291. local eu = http.getenv("HTTP_AUTH_USER")
  292. local ep = http.getenv("HTTP_AUTH_PASS")
  293. if eu and ep and sys.user.checkpasswd(eu, ep) then
  294. authen = function() return eu end
  295. end
  296. end
  297. if not util.contains(accs, user) then
  298. if authen then
  299. local user, sess = authen(sys.user.checkpasswd, accs, def)
  300. local token
  301. if not user or not util.contains(accs, user) then
  302. return
  303. else
  304. if not sess then
  305. local sdat = util.ubus("session", "create", { timeout = tonumber(luci.config.sauth.sessiontime) })
  306. if sdat then
  307. token = sys.uniqueid(16)
  308. util.ubus("session", "set", {
  309. ubus_rpc_session = sdat.ubus_rpc_session,
  310. values = {
  311. user = user,
  312. token = token,
  313. section = sys.uniqueid(16)
  314. }
  315. })
  316. sess = sdat.ubus_rpc_session
  317. end
  318. end
  319. if sess and token then
  320. http.header("Set-Cookie", 'sysauth=%s; path=%s/' %{
  321. sess, build_url()
  322. })
  323. ctx.urltoken.stok = token
  324. ctx.authsession = sess
  325. ctx.authuser = user
  326. http.redirect(build_url(unpack(ctx.requestpath)))
  327. end
  328. end
  329. else
  330. http.status(403, "Forbidden")
  331. return
  332. end
  333. else
  334. ctx.authsession = sess
  335. ctx.authuser = user
  336. end
  337. end
  338. if track.setgroup then
  339. sys.process.setgroup(track.setgroup)
  340. end
  341. if track.setuser then
  342. -- trigger ubus connection before dropping root privs
  343. util.ubus()
  344. sys.process.setuser(track.setuser)
  345. end
  346. local target = nil
  347. if c then
  348. if type(c.target) == "function" then
  349. target = c.target
  350. elseif type(c.target) == "table" then
  351. target = c.target.target
  352. end
  353. end
  354. if c and (c.index or type(target) == "function") then
  355. ctx.dispatched = c
  356. ctx.requested = ctx.requested or ctx.dispatched
  357. end
  358. if c and c.index then
  359. local tpl = require "luci.template"
  360. if util.copcall(tpl.render, "indexer", {}) then
  361. return true
  362. end
  363. end
  364. if type(target) == "function" then
  365. util.copcall(function()
  366. local oldenv = getfenv(target)
  367. local module = require(c.module)
  368. local env = setmetatable({}, {__index=
  369. function(tbl, key)
  370. return rawget(tbl, key) or module[key] or oldenv[key]
  371. end})
  372. setfenv(target, env)
  373. end)
  374. local ok, err
  375. if type(c.target) == "table" then
  376. ok, err = util.copcall(target, c.target, unpack(args))
  377. else
  378. ok, err = util.copcall(target, unpack(args))
  379. end
  380. assert(ok,
  381. "Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
  382. " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
  383. "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
  384. else
  385. local root = node()
  386. if not root or not root.target then
  387. error404("No root node was registered, this usually happens if no module was installed.\n" ..
  388. "Install luci-mod-admin-full and retry. " ..
  389. "If the module is already installed, try removing the /tmp/luci-indexcache file.")
  390. else
  391. error404("No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
  392. "If this url belongs to an extension, make sure it is properly installed.\n" ..
  393. "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
  394. end
  395. end
  396. end
  397. function createindex()
  398. local controllers = { }
  399. local base = "%s/controller/" % util.libpath()
  400. local _, path
  401. for path in (fs.glob("%s*.lua" % base) or function() end) do
  402. controllers[#controllers+1] = path
  403. end
  404. for path in (fs.glob("%s*/*.lua" % base) or function() end) do
  405. controllers[#controllers+1] = path
  406. end
  407. if indexcache then
  408. local cachedate = fs.stat(indexcache, "mtime")
  409. if cachedate then
  410. local realdate = 0
  411. for _, obj in ipairs(controllers) do
  412. local omtime = fs.stat(obj, "mtime")
  413. realdate = (omtime and omtime > realdate) and omtime or realdate
  414. end
  415. if cachedate > realdate and sys.process.info("uid") == 0 then
  416. assert(
  417. sys.process.info("uid") == fs.stat(indexcache, "uid")
  418. and fs.stat(indexcache, "modestr") == "rw-------",
  419. "Fatal: Indexcache is not sane!"
  420. )
  421. index = loadfile(indexcache)()
  422. return index
  423. end
  424. end
  425. end
  426. index = {}
  427. for _, path in ipairs(controllers) do
  428. local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
  429. local mod = require(modname)
  430. assert(mod ~= true,
  431. "Invalid controller file found\n" ..
  432. "The file '" .. path .. "' contains an invalid module line.\n" ..
  433. "Please verify whether the module name is set to '" .. modname ..
  434. "' - It must correspond to the file path!")
  435. local idx = mod.index
  436. assert(type(idx) == "function",
  437. "Invalid controller file found\n" ..
  438. "The file '" .. path .. "' contains no index() function.\n" ..
  439. "Please make sure that the controller contains a valid " ..
  440. "index function and verify the spelling!")
  441. index[modname] = idx
  442. end
  443. if indexcache then
  444. local f = nixio.open(indexcache, "w", 600)
  445. f:writeall(util.get_bytecode(index))
  446. f:close()
  447. end
  448. end
  449. -- Build the index before if it does not exist yet.
  450. function createtree()
  451. if not index then
  452. createindex()
  453. end
  454. local ctx = context
  455. local tree = {nodes={}, inreq=true}
  456. local modi = {}
  457. ctx.treecache = setmetatable({}, {__mode="v"})
  458. ctx.tree = tree
  459. ctx.modifiers = modi
  460. -- Load default translation
  461. require "luci.i18n".loadc("base")
  462. local scope = setmetatable({}, {__index = luci.dispatcher})
  463. for k, v in pairs(index) do
  464. scope._NAME = k
  465. setfenv(v, scope)
  466. v()
  467. end
  468. local function modisort(a,b)
  469. return modi[a].order < modi[b].order
  470. end
  471. for _, v in util.spairs(modi, modisort) do
  472. scope._NAME = v.module
  473. setfenv(v.func, scope)
  474. v.func()
  475. end
  476. return tree
  477. end
  478. function modifier(func, order)
  479. context.modifiers[#context.modifiers+1] = {
  480. func = func,
  481. order = order or 0,
  482. module
  483. = getfenv(2)._NAME
  484. }
  485. end
  486. function assign(path, clone, title, order)
  487. local obj = node(unpack(path))
  488. obj.nodes = nil
  489. obj.module = nil
  490. obj.title = title
  491. obj.order = order
  492. setmetatable(obj, {__index = _create_node(clone)})
  493. return obj
  494. end
  495. function entry(path, target, title, order)
  496. local c = node(unpack(path))
  497. c.target = target
  498. c.title = title
  499. c.order = order
  500. c.module = getfenv(2)._NAME
  501. return c
  502. end
  503. -- enabling the node.
  504. function get(...)
  505. return _create_node({...})
  506. end
  507. function node(...)
  508. local c = _create_node({...})
  509. c.module = getfenv(2)._NAME
  510. c.auto = nil
  511. return c
  512. end
  513. function _create_node(path)
  514. if #path == 0 then
  515. return context.tree
  516. end
  517. local name = table.concat(path, ".")
  518. local c = context.treecache[name]
  519. if not c then
  520. local last = table.remove(path)
  521. local parent = _create_node(path)
  522. c = {nodes={}, auto=true}
  523. -- the node is "in request" if the request path matches
  524. -- at least up to the length of the node path
  525. if parent.inreq and context.path[#path+1] == last then
  526. c.inreq = true
  527. end
  528. parent.nodes[last] = c
  529. context.treecache[name] = c
  530. end
  531. return c
  532. end
  533. -- Subdispatchers --
  534. function _firstchild()
  535. local path = { unpack(context.path) }
  536. local name = table.concat(path, ".")
  537. local node = context.treecache[name]
  538. local lowest
  539. if node and node.nodes and next(node.nodes) then
  540. local k, v
  541. for k, v in pairs(node.nodes) do
  542. if not lowest or
  543. (v.order or 100) < (node.nodes[lowest].order or 100)
  544. then
  545. lowest = k
  546. end
  547. end
  548. end
  549. assert(lowest ~= nil,
  550. "The requested node contains no childs, unable to redispatch")
  551. path[#path+1] = lowest
  552. dispatch(path)
  553. end
  554. function firstchild()
  555. return { type = "firstchild", target = _firstchild }
  556. end
  557. function alias(...)
  558. local req = {...}
  559. return function(...)
  560. for _, r in ipairs({...}) do
  561. req[#req+1] = r
  562. end
  563. dispatch(req)
  564. end
  565. end
  566. function rewrite(n, ...)
  567. local req = {...}
  568. return function(...)
  569. local dispatched = util.clone(context.dispatched)
  570. for i=1,n do
  571. table.remove(dispatched, 1)
  572. end
  573. for i, r in ipairs(req) do
  574. table.insert(dispatched, i, r)
  575. end
  576. for _, r in ipairs({...}) do
  577. dispatched[#dispatched+1] = r
  578. end
  579. dispatch(dispatched)
  580. end
  581. end
  582. local function _call(self, ...)
  583. local func = getfenv()[self.name]
  584. assert(func ~= nil,
  585. 'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?')
  586. assert(type(func) == "function",
  587. 'The symbol "' .. self.name .. '" does not refer to a function but data ' ..
  588. 'of type "' .. type(func) .. '".')
  589. if #self.argv > 0 then
  590. return func(unpack(self.argv), ...)
  591. else
  592. return func(...)
  593. end
  594. end
  595. function call(name, ...)
  596. return {type = "call", argv = {...}, name = name, target = _call}
  597. end
  598. local _template = function(self, ...)
  599. require "luci.template".render(self.view)
  600. end
  601. function template(name)
  602. return {type = "template", view = name, target = _template}
  603. end
  604. local function _cbi(self, ...)
  605. local cbi = require "luci.cbi"
  606. local tpl = require "luci.template"
  607. local http = require "luci.http"
  608. local config = self.config or {}
  609. local maps = cbi.load(self.model, ...)
  610. local state = nil
  611. for i, res in ipairs(maps) do
  612. res.flow = config
  613. local cstate = res:parse()
  614. if cstate and (not state or cstate < state) then
  615. state = cstate
  616. end
  617. end
  618. local function _resolve_path(path)
  619. return type(path) == "table" and build_url(unpack(path)) or path
  620. end
  621. if config.on_valid_to and state and state > 0 and state < 2 then
  622. http.redirect(_resolve_path(config.on_valid_to))
  623. return
  624. end
  625. if config.on_changed_to and state and state > 1 then
  626. http.redirect(_resolve_path(config.on_changed_to))
  627. return
  628. end
  629. if config.on_success_to and state and state > 0 then
  630. http.redirect(_resolve_path(config.on_success_to))
  631. return
  632. end
  633. if config.state_handler then
  634. if not config.state_handler(state, maps) then
  635. return
  636. end
  637. end
  638. http.header("X-CBI-State", state or 0)
  639. if not config.noheader then
  640. tpl.render("cbi/header", {state = state})
  641. end
  642. local redirect
  643. local messages
  644. local applymap = false
  645. local pageaction = true
  646. local parsechain = { }
  647. for i, res in ipairs(maps) do
  648. if res.apply_needed and res.parsechain then
  649. local c
  650. for _, c in ipairs(res.parsechain) do
  651. parsechain[#parsechain+1] = c
  652. end
  653. applymap = true
  654. end
  655. if res.redirect then
  656. redirect = redirect or res.redirect
  657. end
  658. if res.pageaction == false then
  659. pageaction = false
  660. end
  661. if res.message then
  662. messages = messages or { }
  663. messages[#messages+1] = res.message
  664. end
  665. end
  666. for i, res in ipairs(maps) do
  667. res:render({
  668. firstmap = (i == 1),
  669. applymap = applymap,
  670. redirect = redirect,
  671. messages = messages,
  672. pageaction = pageaction,
  673. parsechain = parsechain
  674. })
  675. end
  676. if not config.nofooter then
  677. tpl.render("cbi/footer", {
  678. flow = config,
  679. pageaction = pageaction,
  680. redirect = redirect,
  681. state = state,
  682. autoapply = config.autoapply
  683. })
  684. end
  685. end
  686. function cbi(model, config)
  687. return {type = "cbi", config = config, model = model, target = _cbi}
  688. end
  689. local function _arcombine(self, ...)
  690. local argv = {...}
  691. local target = #argv > 0 and self.targets[2] or self.targets[1]
  692. setfenv(target.target, self.env)
  693. target:target(unpack(argv))
  694. end
  695. function arcombine(trg1, trg2)
  696. return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
  697. end
  698. local function _form(self, ...)
  699. local cbi = require "luci.cbi"
  700. local tpl = require "luci.template"
  701. local http = require "luci.http"
  702. local maps = luci.cbi.load(self.model, ...)
  703. local state = nil
  704. for i, res in ipairs(maps) do
  705. local cstate = res:parse()
  706. if cstate and (not state or cstate < state) then
  707. state = cstate
  708. end
  709. end
  710. http.header("X-CBI-State", state or 0)
  711. tpl.render("header")
  712. for i, res in ipairs(maps) do
  713. res:render()
  714. end
  715. tpl.render("footer")
  716. end
  717. function form(model)
  718. return {type = "cbi", model = model, target = _form}
  719. end
  720. translate = i18n.translate
  721. -- This function does not actually translate the given argument but
  722. -- is used by build/i18n-scan.pl to find translatable entries.
  723. function _(text)
  724. return text
  725. end