2 LuCI - Configuration Bind Interface
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
12 Copyright 2008 Steven Barth <steven@midlink.org>
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
18 http://www.apache.org/licenses/LICENSE-2.0
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
27 module("luci.cbi", package.seeall)
29 require("luci.template")
32 require("luci.model.uci")
34 local uci = luci.model.uci
35 local class = luci.util.class
36 local instanceof = luci.util.instanceof
42 CREATE_PREFIX = "cbi.cts."
43 REMOVE_PREFIX = "cbi.rts."
45 -- Loads a CBI map from given file, creating an environment and returns it
46 function load(cbimap, ...)
49 require("luci.config")
52 local cbidir = luci.util.libpath() .. "/model/cbi/"
53 local func, err = loadfile(cbidir..cbimap..".lua")
59 luci.i18n.loadc("cbi")
61 luci.util.resfenv(func)
62 luci.util.updfenv(func, luci.cbi)
63 luci.util.extfenv(func, "translate", luci.i18n.translate)
64 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
65 luci.util.extfenv(func, "arg", {...})
69 for i, map in ipairs(maps) do
70 if not instanceof(map, Node) then
71 error("CBI map returns no valid map object!")
79 -- Node pseudo abstract class
82 function Node.__init__(self, title, description)
84 self.title = title or ""
85 self.description = description or ""
86 self.template = "cbi/node"
90 function Node._i18n(self, config, section, option, title, description)
93 if type(luci.i18n) == "table" then
95 local key = config:gsub("[^%w]+", "")
97 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
98 if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end
100 self.title = title or luci.i18n.translate( key, option or section or config )
101 self.description = description or luci.i18n.translate( key .. "_desc", "" )
105 -- Append child nodes
106 function Node.append(self, obj)
107 table.insert(self.children, obj)
110 -- Parse this node and its children
111 function Node.parse(self, ...)
112 for k, child in ipairs(self.children) do
118 function Node.render(self, scope)
122 luci.template.render(self.template, scope)
125 -- Render the children
126 function Node.render_children(self, ...)
127 for k, node in ipairs(self.children) do
134 A simple template element
136 Template = class(Node)
138 function Template.__init__(self, template)
140 self.template = template
143 function Template.render(self)
144 luci.template.render(self.template, {self=self})
149 Map - A map describing a configuration file
153 function Map.__init__(self, config, ...)
154 Node.__init__(self, ...)
155 Node._i18n(self, config, nil, nil, ...)
158 self.parsechain = {self.config}
159 self.template = "cbi/map"
160 if not uci.load(self.config) then
161 error("Unable to read UCI data: " .. self.config)
166 -- Chain foreign config
167 function Map.chain(self, config)
168 table.insert(self.parsechain, config)
171 -- Use optimized UCI writing
172 function Map.parse(self, ...)
173 Node.parse(self, ...)
174 for i, config in ipairs(self.parsechain) do
177 if luci.http.formvalue("cbi.apply") then
178 for i, config in ipairs(self.parsechain) do
180 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
181 luci.util.exec(luci.config.uci_oncommit[config])
184 -- Refresh data because commit changes section names
190 Node.parse(self, ...)
193 for i, config in ipairs(self.parsechain) do
198 -- Creates a child section
199 function Map.section(self, class, ...)
200 if instanceof(class, AbstractSection) then
201 local obj = class(self, ...)
205 error("class must be a descendent of AbstractSection")
210 function Map.add(self, sectiontype)
211 return uci.add(self.config, sectiontype)
215 function Map.set(self, section, option, value)
217 return uci.set(self.config, section, option, value)
219 return uci.set(self.config, section, value)
224 function Map.del(self, section, option)
226 return uci.delete(self.config, section, option)
228 return uci.delete(self.config, section)
233 function Map.get(self, section, option)
235 return uci.get_all(self.config)
237 return uci.get(self.config, section, option)
239 return uci.get_all(self.config, section)
244 function Map.stateget(self, section, option)
245 return uci.get_statevalue(self.config, section, option)
250 SimpleForm - A Simple non-UCI form
252 SimpleForm = class(Node)
254 function SimpleForm.__init__(self, config, title, description, data)
255 Node.__init__(self, title, description)
257 self.data = data or {}
258 self.template = "cbi/simpleform"
262 function SimpleForm.parse(self, ...)
263 Node.parse(self, 1, ...)
266 for i, v in ipairs(self.children) do
268 and (not v.tag_missing or not v.tag_missing[1])
269 and (not v.tag_invalid or not v.tag_invalid[1])
273 not luci.http.formvalue("cbi.submit") and 0
277 self.dorender = self:handle(state, self.data)
280 function SimpleForm.render(self, ...)
281 if self.dorender then
282 Node.render(self, ...)
286 -- Creates a child section
287 function SimpleForm.field(self, class, ...)
288 if instanceof(class, AbstractValue) then
289 local obj = class(self, ...)
290 obj.track_missing = true
294 error("class must be a descendent of AbstractValue")
298 function SimpleForm.set(self, section, option, value)
299 self.data[option] = value
303 function SimpleForm.del(self, section, option)
304 self.data[option] = nil
308 function SimpleForm.get(self, section, option)
309 return self.data[option]
317 AbstractSection = class(Node)
319 function AbstractSection.__init__(self, map, sectiontype, ...)
320 Node.__init__(self, ...)
321 self.sectiontype = sectiontype
323 self.config = map.config
328 self.addremove = false
332 -- Appends a new option
333 function AbstractSection.option(self, class, option, ...)
334 if instanceof(class, AbstractValue) then
335 local obj = class(self.map, option, ...)
337 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
342 error("class must be a descendent of AbstractValue")
346 -- Parse optional options
347 function AbstractSection.parse_optionals(self, section)
348 if not self.optional then
352 self.optionals[section] = {}
354 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
355 for k,v in ipairs(self.children) do
356 if v.optional and not v:cfgvalue(section) then
357 if field == v.option then
360 table.insert(self.optionals[section], v)
365 if field and #field > 0 and self.dynamic then
366 self:add_dynamic(field)
370 -- Add a dynamic option
371 function AbstractSection.add_dynamic(self, field, optional)
372 local o = self:option(Value, field, field)
373 o.optional = optional
376 -- Parse all dynamic options
377 function AbstractSection.parse_dynamic(self, section)
378 if not self.dynamic then
382 local arr = luci.util.clone(self:cfgvalue(section))
383 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
384 for k, v in pairs(form) do
388 for key,val in pairs(arr) do
391 for i,c in ipairs(self.children) do
392 if c.option == key then
397 if create and key:sub(1, 1) ~= "." then
398 self:add_dynamic(key, true)
403 -- Returns the section's UCI table
404 function AbstractSection.cfgvalue(self, section)
405 return self.map:get(section)
408 -- Removes the section
409 function AbstractSection.remove(self, section)
410 return self.map:del(section)
413 -- Creates the section
414 function AbstractSection.create(self, section)
418 stat = self.map:set(section, nil, self.sectiontype)
420 section = self.map:add(self.sectiontype)
425 for k,v in pairs(self.children) do
427 self.map:set(section, v.option, v.default)
431 for k,v in pairs(self.defaults) do
432 self.map:set(section, k, v)
442 NamedSection - A fixed configuration section defined by its name
444 NamedSection = class(AbstractSection)
446 function NamedSection.__init__(self, map, section, type, ...)
447 AbstractSection.__init__(self, map, type, ...)
448 Node._i18n(self, map.config, section, nil, ...)
450 self.template = "cbi/nsection"
451 self.section = section
452 self.addremove = false
455 function NamedSection.parse(self)
456 local s = self.section
457 local active = self:cfgvalue(s)
460 if self.addremove then
461 local path = self.config.."."..s
462 if active then -- Remove the section
463 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
466 else -- Create and apply default values
467 if luci.http.formvalue("cbi.cns."..path) then
475 AbstractSection.parse_dynamic(self, s)
476 if luci.http.formvalue("cbi.submit") then
479 AbstractSection.parse_optionals(self, s)
485 TypedSection - A (set of) configuration section(s) defined by the type
486 addremove: Defines whether the user can add/remove sections of this type
487 anonymous: Allow creating anonymous sections
488 validate: a validation function returning nil if the section is invalid
490 TypedSection = class(AbstractSection)
492 function TypedSection.__init__(self, map, type, ...)
493 AbstractSection.__init__(self, map, type, ...)
494 Node._i18n(self, map.config, type, nil, ...)
496 self.template = "cbi/tsection"
499 self.anonymous = false
502 -- Return all matching UCI sections for this TypedSection
503 function TypedSection.cfgsections(self)
505 uci.foreach(self.map.config, self.sectiontype,
507 if self:checkscope(section[".name"]) then
508 table.insert(sections, section[".name"])
515 -- Limits scope to sections that have certain option => value pairs
516 function TypedSection.depends(self, option, value)
517 table.insert(self.deps, {option=option, value=value})
520 function TypedSection.parse(self)
521 if self.addremove then
523 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
524 local name = luci.http.formvalue(crval)
525 if self.anonymous then
531 -- Ignore if it already exists
532 if self:cfgvalue(name) then
536 name = self:checkscope(name)
539 self.err_invalid = true
542 if name and name:len() > 0 then
549 crval = REMOVE_PREFIX .. self.config
550 name = luci.http.formvaluetable(crval)
551 for k,v in pairs(name) do
552 if self:cfgvalue(k) and self:checkscope(k) then
558 for i, k in ipairs(self:cfgsections()) do
559 AbstractSection.parse_dynamic(self, k)
560 if luci.http.formvalue("cbi.submit") then
563 AbstractSection.parse_optionals(self, k)
567 -- Verifies scope of sections
568 function TypedSection.checkscope(self, section)
569 -- Check if we are not excluded
570 if self.filter and not self:filter(section) then
574 -- Check if at least one dependency is met
575 if #self.deps > 0 and self:cfgvalue(section) then
578 for k, v in ipairs(self.deps) do
579 if self:cfgvalue(section)[v.option] == v.value then
589 return self:validate(section)
593 -- Dummy validate function
594 function TypedSection.validate(self, section)
600 AbstractValue - An abstract Value Type
601 null: Value can be empty
602 valid: A function returning the value if it is valid otherwise nil
603 depends: A table of option => value pairs of which one must be true
604 default: The default value
605 size: The size of the input fields
606 rmempty: Unset value if empty
607 optional: This value is optional (see AbstractSection.optionals)
609 AbstractValue = class(Node)
611 function AbstractValue.__init__(self, map, option, ...)
612 Node.__init__(self, ...)
615 self.config = map.config
616 self.tag_invalid = {}
617 self.tag_missing = {}
620 self.track_missing = false
624 self.optional = false
625 self.stateful = false
628 -- Add a dependencie to another section field
629 function AbstractValue.depends(self, field, value)
630 table.insert(self.deps, {field=field, value=value})
633 -- Return whether this object should be created
634 function AbstractValue.formcreated(self, section)
635 local key = "cbi.opt."..self.config.."."..section
636 return (luci.http.formvalue(key) == self.option)
639 -- Returns the formvalue for this object
640 function AbstractValue.formvalue(self, section)
641 local key = "cbid."..self.map.config.."."..section.."."..self.option
642 return luci.http.formvalue(key)
645 function AbstractValue.additional(self, value)
646 self.optional = value
649 function AbstractValue.mandatory(self, value)
650 self.rmempty = not value
653 function AbstractValue.parse(self, section)
654 local fvalue = self:formvalue(section)
655 local cvalue = self:cfgvalue(section)
657 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
658 fvalue = self:transform(self:validate(fvalue, section))
660 self.tag_invalid[section] = true
662 if fvalue and not (fvalue == cvalue) then
663 self:write(section, fvalue)
665 else -- Unset the UCI or error
666 if self.rmempty or self.optional then
668 elseif self.track_missing and not fvalue or fvalue ~= cvalue then
669 self.tag_missing[section] = true
674 -- Render if this value exists or if it is mandatory
675 function AbstractValue.render(self, s, scope)
676 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
679 scope.cbid = "cbid." .. self.config ..
683 scope.ifattr = function(cond,key,val)
685 return string.format(
686 ' %s="%s"', tostring(key),
689 or (type(self[key]) ~= "function" and self[key])
697 scope.attr = function(...)
698 return scope.ifattr( true, ... )
701 Node.render(self, scope)
705 -- Return the UCI value of this object
706 function AbstractValue.cfgvalue(self, section)
708 and self.map:stateget(section, self.option)
709 or self.map:get(section, self.option)
712 -- Validate the form value
713 function AbstractValue.validate(self, value)
717 AbstractValue.transform = AbstractValue.validate
721 function AbstractValue.write(self, section, value)
722 return self.map:set(section, self.option, value)
726 function AbstractValue.remove(self, section)
727 return self.map:del(section, self.option)
734 Value - A one-line value
735 maxlength: The maximum length
737 Value = class(AbstractValue)
739 function Value.__init__(self, ...)
740 AbstractValue.__init__(self, ...)
741 self.template = "cbi/value"
746 function Value.value(self, key, val)
748 table.insert(self.keylist, tostring(key))
749 table.insert(self.vallist, tostring(val))
753 -- DummyValue - This does nothing except being there
754 DummyValue = class(AbstractValue)
756 function DummyValue.__init__(self, map, ...)
757 AbstractValue.__init__(self, map, ...)
758 self.template = "cbi/dvalue"
762 function DummyValue.parse(self)
768 Flag - A flag being enabled or disabled
770 Flag = class(AbstractValue)
772 function Flag.__init__(self, ...)
773 AbstractValue.__init__(self, ...)
774 self.template = "cbi/fvalue"
780 -- A flag can only have two states: set or unset
781 function Flag.parse(self, section)
782 local fvalue = self:formvalue(section)
785 fvalue = self.enabled
787 fvalue = self.disabled
790 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
791 if not(fvalue == self:cfgvalue(section)) then
792 self:write(section, fvalue)
802 ListValue - A one-line value predefined in a list
803 widget: The widget that will be used (select, radio)
805 ListValue = class(AbstractValue)
807 function ListValue.__init__(self, ...)
808 AbstractValue.__init__(self, ...)
809 self.template = "cbi/lvalue"
814 self.widget = "select"
817 function ListValue.value(self, key, val)
819 table.insert(self.keylist, tostring(key))
820 table.insert(self.vallist, tostring(val))
823 function ListValue.validate(self, val)
824 if luci.util.contains(self.keylist, val) then
834 MultiValue - Multiple delimited values
835 widget: The widget that will be used (select, checkbox)
836 delimiter: The delimiter that will separate the values (default: " ")
838 MultiValue = class(AbstractValue)
840 function MultiValue.__init__(self, ...)
841 AbstractValue.__init__(self, ...)
842 self.template = "cbi/mvalue"
846 self.widget = "checkbox"
850 function MultiValue.render(self, ...)
851 if self.widget == "select" and not self.size then
852 self.size = #self.vallist
855 AbstractValue.render(self, ...)
858 function MultiValue.value(self, key, val)
860 table.insert(self.keylist, tostring(key))
861 table.insert(self.vallist, tostring(val))
864 function MultiValue.valuelist(self, section)
865 local val = self:cfgvalue(section)
867 if not(type(val) == "string") then
871 return luci.util.split(val, self.delimiter)
874 function MultiValue.validate(self, val)
875 val = (type(val) == "table") and val or {val}
879 for i, value in ipairs(val) do
880 if luci.util.contains(self.keylist, value) then
881 result = result and (result .. self.delimiter .. value) or value
889 TextValue - A multi-line value
892 TextValue = class(AbstractValue)
894 function TextValue.__init__(self, ...)
895 AbstractValue.__init__(self, ...)
896 self.template = "cbi/tvalue"