X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fcbi%2Fluasrc%2Fcbi.lua;h=e687164c25f1dc8e3fba9a98d89fc0dc3f8f49aa;hp=b6ccc548065bfdf4731021027af78766022cfeb1;hb=3cb299dc1077b7edee16e48255073603a3c694d8;hpb=ad57125f4132bef88ff0d2b4e34281ff812dc08b diff --git a/libs/cbi/luasrc/cbi.lua b/libs/cbi/luasrc/cbi.lua index b6ccc5480..e687164c2 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 @@ -40,8 +41,10 @@ local instanceof = util.instanceof FORM_NODATA = 0 FORM_PROCEED = 0 FORM_VALID = 1 +FORM_DONE = 1 FORM_INVALID = -1 FORM_CHANGED = 2 +FORM_SKIP = 4 AUTO = true @@ -50,25 +53,26 @@ 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/" + local func, err - assert(luci.fs.stat(cbimap) or luci.fs.stat(cbidir..cbimap..".lua"), - "Model not found!") - - local func, err = loadfile(cbimap) - if not func then + if fs.access(cbidir..cbimap..".lua") then func, err = loadfile(cbidir..cbimap..".lua") + elseif fs.access(cbimap) then + func, err = loadfile(cbimap) + 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, @@ -166,8 +170,8 @@ local function _uvl_validate_section(node, name) local function tag_section(e) local s = { } - for _, c in ipairs(e.childs) do - if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then + for _, c in ipairs(e.childs or { e }) do + if c.childs and not c:is('DEPENDENCY') then table.insert( s, c.childs[1]:string() ) else table.insert( s, c:string() ) @@ -215,20 +219,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 @@ -280,6 +287,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 @@ -288,13 +300,14 @@ Map = class(Node) function Map.__init__(self, config, ...) Node.__init__(self, ...) - Node._i18n(self, config, nil, nil, ...) self.config = config self.parsechain = {self.config} self.template = "cbi/map" self.apply_on_parse = nil self.readinput = true + self.proceed = false + self.flow = {} self.uci = uci.cursor() self.save = true @@ -307,7 +320,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) @@ -315,7 +327,7 @@ function Map.formvalue(self, key) end function Map.formvaluetable(self, key) - return self.readinput and luci.http.formvaluetable(key) + return self.readinput and luci.http.formvaluetable(key) or {} end function Map.get_scheme(self, sectiontype, option) @@ -343,21 +355,31 @@ 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 + return self:state_handler(self.state) + end + Node.parse(self, ...) if self.save then for i, config in ipairs(self.parsechain) do self.uci:save(config) end - if self:submitstate() and (self.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) @@ -378,10 +400,12 @@ function Map.parse(self, readinput, ...) end if self:submitstate() then - if self.save then - self.state = self.changed and FORM_CHANGED or FORM_VALID - else + if not self.save then self.state = FORM_INVALID + elseif self.proceed then + self.state = FORM_PROCEED + else + self.state = self.changed and FORM_CHANGED or FORM_VALID end else self.state = FORM_NODATA @@ -391,11 +415,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 @@ -451,11 +477,18 @@ Compound = class(Node) function Compound.__init__(self, ...) Node.__init__(self) + self.template = "cbi/compound" self.children = {...} end +function Compound.populate_delegator(self, delegator) + for _, v in ipairs(self.children) do + v.delegator = delegator + end +end + function Compound.parse(self, ...) - local cstate, state = 0, 0 + local cstate, state = 0 for k, child in ipairs(self.children) do cstate = child:parse(...) @@ -473,60 +506,150 @@ Delegator = class(Node) function Delegator.__init__(self, ...) Node.__init__(self, ...) self.nodes = {} + 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.state(self, name, node, transitor) - transitor = transitor or self.transistor_linear - local state = {node=node, name=name, transitor=transitor} - - assert(instanceof(node, Node), "Invalid node") +function Delegator.set(self, name, node) assert(not self.nodes[name], "Duplicate entry") - self.nodes[name] = state - self:append(state) + self.nodes[name] = node +end - return state +function Delegator.add(self, name, node) + node = self:set(name, node) + self.defaultpath[#self.defaultpath+1] = name end -function Delegator.get(self, name) - return self.nodes[name] +function Delegator.insert_after(self, name, after) + local n = #self.chain + 1 + for k, v in ipairs(self.chain) do + if v == after then + n = k + 1 + break + end + end + table.insert(self.chain, n, name) end -function Delegator.transistor_linear(self, state, cstate) - if cstate > 0 then - for i, child in ipairs(self.children) do - if state == child then - return self.children[i+1] - end +function Delegator.set_route(self, ...) + local n, chain, route = 0, self.chain, {...} + for i = 1, #chain do + if chain[i] == self.current then + n = i + break end - else - return state + end + for i = 1, #route do + n = n + 1 + chain[n] = route[i] + end + for i = n + 1, #chain do + chain[i] = nil end end +function Delegator.get(self, 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, ...) - local active = self:getactive() - assert(active, "Invalid state") + 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() + self.active = self.active or self:get(self.current) + assert(self.active, "Invalid state") + + local stat = FORM_DONE + if type(self.active) ~= "function" then + self.active:populate_delegator(self) + stat = self.active:parse() + else + self:active() + end - local cstate = active.node:parse() - self.active = active.transistor(self, active.node, cstate) + if stat > FORM_PROCEED then + if Map.formvalue(self, "cbi.delg.back") then + newcurrent = self:get_prev(self.current) + else + newcurrent = self:get_next(self.current) + end + elseif stat < FORM_PROCEED then + return stat + end + - if not self.active 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.active:parse(false) - return FROM_PROCEED + self.current = newcurrent or self.current + self.active = self:get(self.current) + if type(self.active) ~= "function" then + 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 end end -function Delegator.render(self, ...) - self.active.node:render(...) +function Delegator.get_next(self, state) + for k, v in ipairs(self.chain) do + if v == state then + return self.chain[k+1] + end + end +end + +function Delegator.get_prev(self, state) + for k, v in ipairs(self.chain) do + if v == state then + return self.chain[k-1] + end + end end -function Delegator.getactive(self) - return self:get(Map.formvalue(self, "cbi.delegated") - or (self.children[1] and self.children[1].name)) +function Delegator.get_chain(self) + local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath + return type(x) == "table" and x or {x} +end + +function Delegator.get_active(self) + return Map.formvalue(self, "cbi.delg.current") or self.chain[1] end --[[ @@ -558,6 +681,15 @@ SimpleForm.formvaluetable = Map.formvaluetable function SimpleForm.parse(self, readinput, ...) self.readinput = (readinput ~= false) + + if self:formvalue("cbi.skip") then + 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 @@ -577,7 +709,12 @@ function SimpleForm.parse(self, readinput, ...) or valid and FORM_VALID or FORM_INVALID - self.dorender = not self.handle or self:handle(state, self.data) ~= false + self.dorender = not self.handle + if self.handle then + local nrender, nstate = self:handle(state, self.data) + self.dorender = self.dorender or (nrender ~= false) + state = nstate or state + end return state end @@ -675,6 +812,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 @@ -693,9 +843,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 @@ -706,6 +853,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 @@ -719,6 +891,7 @@ function AbstractSection.parse_optionals(self, section) if v.optional and not v:cfgvalue(section) then if field == v.option then field = nil + self.map.proceed = true else table.insert(self.optionals[section], v) end @@ -758,6 +931,7 @@ function AbstractSection.parse_dynamic(self, section) end if create and key:sub(1, 1) ~= "." then + self.map.proceed = true self:add_dynamic(key, true) end end @@ -776,7 +950,7 @@ end -- Removes the section function AbstractSection.remove(self, section) - self.map.autoapply = false + self.map.proceed = true return self.map:del(section) end @@ -785,7 +959,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 @@ -803,7 +977,7 @@ function AbstractSection.create(self, section) end end - self.map.autoapply = false + self.map.proceed = true return stat end @@ -883,7 +1057,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 @@ -948,7 +1121,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 = {} @@ -1107,6 +1279,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 @@ -1184,6 +1357,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 @@ -1192,6 +1381,11 @@ function AbstractValue.parse(self, section, novld) else self.error = { [section] = "invalid" } end + if self.section.error then + table.insert(self.section.error[section], "invalid") + else + self.section.error = {[section] = {"invalid"}} + end self.map.save = false end if fvalue and not (fvalue == cvalue) then @@ -1227,6 +1421,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 @@ -1415,7 +1610,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 @@ -1537,6 +1732,11 @@ function DynamicList.value(self, key, val) table.insert(self.vallist, tostring(val)) end +function DynamicList.write(self, ...) + self.map.proceed = true + return AbstractValue.write(self, ...) +end + function DynamicList.formvalue(self, section) local value = AbstractValue.formvalue(self, section) value = (type(value) == "table") and value or {value} @@ -1598,7 +1798,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 @@ -1612,7 +1812,7 @@ function FileUpload.formvalue(self, section) then return val end - luci.fs.unlink(val) + fs.unlink(val) self.value = nil end return nil @@ -1620,7 +1820,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