X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fcbi%2Fluasrc%2Fcbi.lua;h=c9db8beb255dd5e201ef29a92d5ad777ecf5879a;hp=e513053bab674f1eb5f6210a78cc0ec998e60328;hb=1f83b916ac83eff03e582084dd99579689570360;hpb=341f757255caa08ee2922a3f32a00849e306f23c diff --git a/libs/cbi/luasrc/cbi.lua b/libs/cbi/luasrc/cbi.lua index e513053ba..c9db8beb2 100644 --- a/libs/cbi/luasrc/cbi.lua +++ b/libs/cbi/luasrc/cbi.lua @@ -30,9 +30,10 @@ require("luci.template") local util = require("luci.util") require("luci.http") require("luci.uvl") -require("luci.fs") + --local event = require "luci.sys.event" +local fs = require("nixio.fs") local uci = require("luci.model.uci") local class = util.class local instanceof = util.instanceof @@ -52,28 +53,28 @@ REMOVE_PREFIX = "cbi.rts." -- Loads a CBI map from given file, creating an environment and returns it function load(cbimap, ...) - require("luci.fs") + local fs = require "nixio.fs" local i18n = require "luci.i18n" require("luci.config") require("luci.util") local upldir = "/lib/uci/upload/" local cbidir = luci.util.libpath() .. "/model/cbi/" - - assert(luci.fs.stat(cbimap) or - luci.fs.stat(cbidir..cbimap..".lua") or - luci.fs.stat(cbidir..cbimap..".lua.gz"), - "Model not found!") - - local func, err = loadfile(cbimap) - if not func then - func, err = loadfile(cbidir..cbimap..".lua") or - loadfile(cbidir..cbimap..".lua.gz") + local func, err + + if fs.access(cbimap) then + func, err = loadfile(cbimap) + elseif fs.access(cbidir..cbimap..".lua") then + func, err = loadfile(cbidir..cbimap..".lua") + elseif fs.access(cbidir..cbimap..".lua.gz") then + func, err = loadfile(cbidir..cbimap..".lua.gz") + else + func, err = nil, "Model '" .. cbimap .. "' not found!" end + assert(func, err) - luci.i18n.loadc("cbi") - luci.i18n.loadc("uvl") + luci.i18n.loadc("base") local env = { translate=i18n.translate, @@ -220,20 +221,23 @@ function Node.__init__(self, title, description) self.template = "cbi/node" end --- i18n helper -function Node._i18n(self, config, section, option, title, description) - - -- i18n loaded? - if type(luci.i18n) == "table" then - - local key = config and config:gsub("[^%w]+", "") or "" - - if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end - if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end +-- hook helper +function Node._run_hook(self, hook) + if type(self[hook]) == "function" then + return self[hook](self) + end +end - self.title = title or luci.i18n.translate( key, option or section or config ) - self.description = description or luci.i18n.translate( key .. "_desc", "" ) +function Node._run_hooks(self, ...) + local f + local r = false + for _, f in ipairs(arg) do + if type(self[f]) == "function" then + self[f](self) + r = true + end end + return r end -- Prepare nodes @@ -285,6 +289,11 @@ function Template.render(self) luci.template.render(self.template, {self=self}) end +function Template.parse(self, readinput) + self.readinput = (readinput ~= false) + return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA +end + --[[ Map - A map describing a configuration file @@ -293,7 +302,6 @@ Map = class(Node) function Map.__init__(self, config, ...) Node.__init__(self, ...) - Node._i18n(self, config, nil, nil, ...) self.config = config self.parsechain = {self.config} @@ -314,7 +322,6 @@ function Map.__init__(self, config, ...) self.validator = luci.uvl.UVL() self.scheme = self.validator:get_scheme(self.config) - end function Map.formvalue(self, key) @@ -350,6 +357,7 @@ end -- Use optimized UCI writing function Map.parse(self, readinput, ...) self.readinput = (readinput ~= false) + self:_run_hooks("on_parse") if self:formvalue("cbi.skip") then self.state = FORM_SKIP @@ -362,15 +370,18 @@ function Map.parse(self, readinput, ...) for i, config in ipairs(self.parsechain) do self.uci:save(config) end - if self:submitstate() and not self.proceed and (self.flow.autoapply or luci.http.formvalue("cbi.apply")) then + if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then + self:_run_hooks("on_before_commit") for i, config in ipairs(self.parsechain) do self.uci:commit(config) -- Refresh data because commit changes section names self.uci:load(config) end + self:_run_hooks("on_commit", "on_after_commit", "on_before_apply") if self.apply_on_parse then self.uci:apply(self.parsechain) + self:_run_hooks("on_apply", "on_after_apply") else self._apply = function() local cmd = self.uci:apply(self.parsechain, true) @@ -406,11 +417,13 @@ function Map.parse(self, readinput, ...) end function Map.render(self, ...) + self:_run_hooks("on_init") Node.render(self, ...) if self._apply then local fp = self._apply() fp:read("*a") fp:close() + self:_run_hooks("on_apply") end end @@ -498,16 +511,14 @@ function Delegator.__init__(self, ...) self.defaultpath = {} self.pageaction = false self.readinput = true + self.allow_reset = false + self.allow_cancel = false self.allow_back = false self.allow_finish = false self.template = "cbi/delegator" end function Delegator.set(self, name, node) - if type(node) == "table" and getmetatable(node) == nil then - node = Compound(unpack(node)) - end - assert(type(node) == "function" or instanceof(node, Compound), "Invalid") assert(not self.nodes[name], "Duplicate entry") self.nodes[name] = node @@ -519,9 +530,9 @@ function Delegator.add(self, name, node) end function Delegator.insert_after(self, name, after) - local n = #self.chain + local n = #self.chain + 1 for k, v in ipairs(self.chain) do - if v == state then + if v == after then n = k + 1 break end @@ -547,10 +558,30 @@ function Delegator.set_route(self, ...) end function Delegator.get(self, name) - return self.nodes[name] + local node = self.nodes[name] + + if type(node) == "string" then + node = load(node, name) + end + + if type(node) == "table" and getmetatable(node) == nil then + node = Compound(unpack(node)) + end + + return node end function Delegator.parse(self, ...) + if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then + if self:_run_hooks("on_cancel") then + return FORM_DONE + end + end + + if not Map.formvalue(self, "cbi.delg.current") then + self:_run_hooks("on_init") + end + local newcurrent self.chain = self.chain or self:get_chain() self.current = self.current or self:get_active() @@ -571,16 +602,27 @@ function Delegator.parse(self, ...) else newcurrent = self:get_next(self.current) end + elseif stat < FORM_PROCEED then + return stat end + - if not newcurrent or not self:get(newcurrent) then - return FORM_DONE + if not Map.formvalue(self, "cbi.submit") then + return FORM_NODATA + elseif stat > FORM_PROCEED + and (not newcurrent or not self:get(newcurrent)) then + return self:_run_hook("on_done") or FORM_DONE else - self.current = newcurrent + self.current = newcurrent or self.current self.active = self:get(self.current) if type(self.active) ~= "function" then - self.active:parse(false) - return FROM_PROCEED + self.active:populate_delegator(self) + local stat = self.active:parse(false) + if stat == FORM_SKIP then + return self:parse(...) + else + return FORM_PROCEED + end else return self:parse(...) end @@ -646,6 +688,10 @@ function SimpleForm.parse(self, readinput, ...) return FORM_SKIP end + if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then + return FORM_DONE + end + if self:submitstate() then Node.parse(self, 1, ...) end @@ -768,6 +814,19 @@ function AbstractSection.__init__(self, map, sectiontype, ...) self.dynamic = false end +-- Define a tab for the section +function AbstractSection.tab(self, tab, title, desc) + self.tabs = self.tabs or { } + self.tab_names = self.tab_names or { } + + self.tab_names[#self.tab_names+1] = tab + self.tabs[tab] = { + title = title, + description = desc, + childs = { } + } +end + -- Appends a new option function AbstractSection.option(self, class, option, ...) -- Autodetect from UVL @@ -786,9 +845,6 @@ function AbstractSection.option(self, class, option, ...) if instanceof(class, AbstractValue) then local obj = class(self.map, self, option, ...) - - Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...) - self:append(obj) self.fields[option] = obj return obj @@ -799,6 +855,31 @@ function AbstractSection.option(self, class, option, ...) end end +-- Appends a new tabbed option +function AbstractSection.taboption(self, tab, ...) + + assert(tab and self.tabs and self.tabs[tab], + "Cannot assign option to not existing tab %q" % tostring(tab)) + + local l = self.tabs[tab].childs + local o = AbstractSection.option(self, ...) + + if o then l[#l+1] = o end + + return o +end + +-- Render a single tab +function AbstractSection.render_tab(self, tab, ...) + + assert(tab and self.tabs and self.tabs[tab], + "Cannot render not existing tab %q" % tostring(tab)) + + for _, node in ipairs(self.tabs[tab].childs) do + node:render(...) + end +end + -- Parse optional options function AbstractSection.parse_optionals(self, section) if not self.optional then @@ -880,7 +961,7 @@ function AbstractSection.create(self, section) local stat if section then - stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype) + stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype) else section = self.map:add(self.sectiontype) stat = section @@ -978,7 +1059,6 @@ NamedSection = class(AbstractSection) function NamedSection.__init__(self, map, section, stype, ...) AbstractSection.__init__(self, map, stype, ...) - Node._i18n(self, map.config, section, nil, ...) -- Defaults self.addremove = false @@ -1043,7 +1123,6 @@ TypedSection = class(AbstractSection) function TypedSection.__init__(self, map, type, ...) AbstractSection.__init__(self, map, type, ...) - Node._i18n(self, map.config, type, nil, ...) self.template = "cbi/tsection" self.deps = {} @@ -1202,6 +1281,7 @@ function AbstractValue.__init__(self, map, section, option, ...) self.tag_reqerror = {} self.tag_error = {} self.deps = {} + self.subdeps = {} --self.cast = "string" self.track_missing = false @@ -1279,6 +1359,22 @@ function AbstractValue.parse(self, section, novld) local fvalue = self:formvalue(section) local cvalue = self:cfgvalue(section) + -- If favlue and cvalue are both tables and have the same content + -- make them identical + if type(fvalue) == "table" and type(cvalue) == "table" then + local equal = #fvalue == #cvalue + if equal then + for i=1, #fvalue do + if cvalue[i] ~= fvalue[i] then + equal = false + end + end + end + if equal then + fvalue = cvalue + end + end + if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI fvalue = self:transform(self:validate(fvalue, section)) if not fvalue and not novld then @@ -1327,6 +1423,7 @@ function AbstractValue.render(self, s, scope) scope.section = s scope.cbid = self:cbid(s) scope.striptags = luci.util.striptags + scope.pcdata = luci.util.pcdata scope.ifattr = function(cond,key,val) if cond then @@ -1515,7 +1612,7 @@ function ListValue.value(self, key, val, ...) table.insert(self.vallist, tostring(val)) for i, deps in ipairs({...}) do - table.insert(self.deps, {add = "-"..key, deps=deps}) + self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps} end end @@ -1703,7 +1800,7 @@ end function FileUpload.cfgvalue(self, section) local val = AbstractValue.cfgvalue(self, section) - if val and luci.fs.access(val) then + if val and fs.access(val) then return val end return nil @@ -1717,7 +1814,7 @@ function FileUpload.formvalue(self, section) then return val end - luci.fs.unlink(val) + fs.unlink(val) self.value = nil end return nil @@ -1725,7 +1822,7 @@ end function FileUpload.remove(self, section) local val = AbstractValue.formvalue(self, section) - if val and luci.fs.access(val) then luci.fs.unlink(val) end + if val and fs.access(val) then fs.unlink(val) end return AbstractValue.remove(self, section) end