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
145 Map - A map describing a configuration file
149 function Map.__init__(self, config, ...)
150 Node.__init__(self, ...)
151 Node._i18n(self, config, nil, nil, ...)
154 self.parsechain = {self.config}
155 self.template = "cbi/map"
156 if not uci.load(self.config) then
157 error("Unable to read UCI data: " .. self.config)
162 -- Chain foreign config
163 function Map.chain(self, config)
164 table.insert(self.parsechain, config)
167 -- Use optimized UCI writing
168 function Map.parse(self, ...)
169 Node.parse(self, ...)
170 for i, config in ipairs(self.parsechain) do
173 if luci.http.formvalue("cbi.apply") then
174 for i, config in ipairs(self.parsechain) do
176 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
177 luci.util.exec(luci.config.uci_oncommit[config])
180 -- Refresh data because commit changes section names
186 Node.parse(self, ...)
189 for i, config in ipairs(self.parsechain) do
194 -- Creates a child section
195 function Map.section(self, class, ...)
196 if instanceof(class, AbstractSection) then
197 local obj = class(self, ...)
201 error("class must be a descendent of AbstractSection")
206 function Map.add(self, sectiontype)
207 return uci.add(self.config, sectiontype)
211 function Map.set(self, section, option, value)
213 return uci.set(self.config, section, option, value)
215 return uci.set(self.config, section, value)
220 function Map.del(self, section, option)
222 return uci.delete(self.config, section, option)
224 return uci.delete(self.config, section)
229 function Map.get(self, section, option)
231 return uci.get_all(self.config)
233 return uci.get(self.config, section, option)
235 return uci.get_all(self.config, section)
240 function Map.stateget(self, section, option)
241 return uci.get_statevalue(self.config, section, option)
246 SimpleForm - A Simple non-UCI form
248 SimpleForm = class(Node)
250 function SimpleForm.__init__(self, config, title, description, data)
251 Node.__init__(self, title, description)
253 self.data = data or {}
254 self.template = "cbi/simpleform"
258 function SimpleForm.parse(self, ...)
259 Node.parse(self, 1, ...)
262 for i, v in ipairs(self.children) do
263 valid = valid and not v.tag_missing[1] and not v.tag_invalid[1]
267 not luci.http.formvalue("cbi.submit") and 0
271 self.dorender = self:handle(state)
274 function SimpleForm.render(self, ...)
275 if self.dorender then
276 Node.render(self, ...)
280 -- Creates a child section
281 function SimpleForm.field(self, class, ...)
282 if instanceof(class, AbstractValue) then
283 local obj = class(self, ...)
287 error("class must be a descendent of AbstractValue")
291 function SimpleForm.set(self, section, option, value)
292 self.data[option] = value
296 function SimpleForm.del(self, section, option)
297 self.data[option] = nil
301 function SimpleForm.get(self, section, option)
302 return self.data[option]
310 AbstractSection = class(Node)
312 function AbstractSection.__init__(self, map, sectiontype, ...)
313 Node.__init__(self, ...)
314 self.sectiontype = sectiontype
316 self.config = map.config
321 self.addremove = false
325 -- Appends a new option
326 function AbstractSection.option(self, class, option, ...)
327 if instanceof(class, AbstractValue) then
328 local obj = class(self.map, option, ...)
330 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
335 error("class must be a descendent of AbstractValue")
339 -- Parse optional options
340 function AbstractSection.parse_optionals(self, section)
341 if not self.optional then
345 self.optionals[section] = {}
347 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
348 for k,v in ipairs(self.children) do
349 if v.optional and not v:cfgvalue(section) then
350 if field == v.option then
353 table.insert(self.optionals[section], v)
358 if field and #field > 0 and self.dynamic then
359 self:add_dynamic(field)
363 -- Add a dynamic option
364 function AbstractSection.add_dynamic(self, field, optional)
365 local o = self:option(Value, field, field)
366 o.optional = optional
369 -- Parse all dynamic options
370 function AbstractSection.parse_dynamic(self, section)
371 if not self.dynamic then
375 local arr = luci.util.clone(self:cfgvalue(section))
376 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
377 for k, v in pairs(form) do
381 for key,val in pairs(arr) do
384 for i,c in ipairs(self.children) do
385 if c.option == key then
390 if create and key:sub(1, 1) ~= "." then
391 self:add_dynamic(key, true)
396 -- Returns the section's UCI table
397 function AbstractSection.cfgvalue(self, section)
398 return self.map:get(section)
401 -- Removes the section
402 function AbstractSection.remove(self, section)
403 return self.map:del(section)
406 -- Creates the section
407 function AbstractSection.create(self, section)
411 stat = self.map:set(section, nil, self.sectiontype)
413 section = self.map:add(self.sectiontype)
418 for k,v in pairs(self.children) do
420 self.map:set(section, v.option, v.default)
424 for k,v in pairs(self.defaults) do
425 self.map:set(section, k, v)
435 NamedSection - A fixed configuration section defined by its name
437 NamedSection = class(AbstractSection)
439 function NamedSection.__init__(self, map, section, type, ...)
440 AbstractSection.__init__(self, map, type, ...)
441 Node._i18n(self, map.config, section, nil, ...)
443 self.template = "cbi/nsection"
444 self.section = section
445 self.addremove = false
448 function NamedSection.parse(self)
449 local s = self.section
450 local active = self:cfgvalue(s)
453 if self.addremove then
454 local path = self.config.."."..s
455 if active then -- Remove the section
456 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
459 else -- Create and apply default values
460 if luci.http.formvalue("cbi.cns."..path) then
468 AbstractSection.parse_dynamic(self, s)
469 if luci.http.formvalue("cbi.submit") then
472 AbstractSection.parse_optionals(self, s)
478 TypedSection - A (set of) configuration section(s) defined by the type
479 addremove: Defines whether the user can add/remove sections of this type
480 anonymous: Allow creating anonymous sections
481 validate: a validation function returning nil if the section is invalid
483 TypedSection = class(AbstractSection)
485 function TypedSection.__init__(self, map, type, ...)
486 AbstractSection.__init__(self, map, type, ...)
487 Node._i18n(self, map.config, type, nil, ...)
489 self.template = "cbi/tsection"
492 self.anonymous = false
495 -- Return all matching UCI sections for this TypedSection
496 function TypedSection.cfgsections(self)
498 uci.foreach(self.map.config, self.sectiontype,
500 if self:checkscope(section[".name"]) then
501 table.insert(sections, section[".name"])
508 -- Limits scope to sections that have certain option => value pairs
509 function TypedSection.depends(self, option, value)
510 table.insert(self.deps, {option=option, value=value})
513 function TypedSection.parse(self)
514 if self.addremove then
516 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
517 local name = luci.http.formvalue(crval)
518 if self.anonymous then
524 -- Ignore if it already exists
525 if self:cfgvalue(name) then
529 name = self:checkscope(name)
532 self.err_invalid = true
535 if name and name:len() > 0 then
542 crval = REMOVE_PREFIX .. self.config
543 name = luci.http.formvaluetable(crval)
544 for k,v in pairs(name) do
545 if self:cfgvalue(k) and self:checkscope(k) then
551 for i, k in ipairs(self:cfgsections()) do
552 AbstractSection.parse_dynamic(self, k)
553 if luci.http.formvalue("cbi.submit") then
556 AbstractSection.parse_optionals(self, k)
560 -- Verifies scope of sections
561 function TypedSection.checkscope(self, section)
562 -- Check if we are not excluded
563 if self.filter and not self:filter(section) then
567 -- Check if at least one dependency is met
568 if #self.deps > 0 and self:cfgvalue(section) then
571 for k, v in ipairs(self.deps) do
572 if self:cfgvalue(section)[v.option] == v.value then
582 return self:validate(section)
586 -- Dummy validate function
587 function TypedSection.validate(self, section)
593 AbstractValue - An abstract Value Type
594 null: Value can be empty
595 valid: A function returning the value if it is valid otherwise nil
596 depends: A table of option => value pairs of which one must be true
597 default: The default value
598 size: The size of the input fields
599 rmempty: Unset value if empty
600 optional: This value is optional (see AbstractSection.optionals)
602 AbstractValue = class(Node)
604 function AbstractValue.__init__(self, map, option, ...)
605 Node.__init__(self, ...)
608 self.config = map.config
609 self.tag_invalid = {}
610 self.tag_missing = {}
616 self.optional = false
617 self.stateful = false
620 -- Add a dependencie to another section field
621 function AbstractValue.depends(self, field, value)
622 table.insert(self.deps, {field=field, value=value})
625 -- Return whether this object should be created
626 function AbstractValue.formcreated(self, section)
627 local key = "cbi.opt."..self.config.."."..section
628 return (luci.http.formvalue(key) == self.option)
631 -- Returns the formvalue for this object
632 function AbstractValue.formvalue(self, section)
633 local key = "cbid."..self.map.config.."."..section.."."..self.option
634 return luci.http.formvalue(key)
637 function AbstractValue.additional(self, value)
638 self.optional = value
641 function AbstractValue.mandatory(self, value)
642 self.rmempty = not value
645 function AbstractValue.parse(self, section)
646 local fvalue = self:formvalue(section)
647 local cvalue = self:cfgvalue(section)
649 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
650 fvalue = self:transform(self:validate(fvalue))
652 self.tag_invalid[section] = true
654 if fvalue and not (fvalue == self:cfgvalue(section)) then
655 self:write(section, fvalue)
657 else -- Unset the UCI or error
658 if self.rmempty or self.optional then
660 elseif not fvalue or fvalue ~= cvalue then
661 --self.tag_missing[section] = true
666 -- Render if this value exists or if it is mandatory
667 function AbstractValue.render(self, s, scope)
668 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
671 scope.cbid = "cbid." .. self.config ..
675 scope.ifattr = function(cond,key,val)
677 return string.format(
678 ' %s="%s"', tostring(key),
681 or (type(self[key]) ~= "function" and self[key])
689 scope.attr = function(...)
690 return scope.ifattr( true, ... )
693 Node.render(self, scope)
697 -- Return the UCI value of this object
698 function AbstractValue.cfgvalue(self, section)
700 and self.map:stateget(section, self.option)
701 or self.map:get(section, self.option)
704 -- Validate the form value
705 function AbstractValue.validate(self, value)
709 AbstractValue.transform = AbstractValue.validate
713 function AbstractValue.write(self, section, value)
714 return self.map:set(section, self.option, value)
718 function AbstractValue.remove(self, section)
719 return self.map:del(section, self.option)
726 Value - A one-line value
727 maxlength: The maximum length
729 Value = class(AbstractValue)
731 function Value.__init__(self, ...)
732 AbstractValue.__init__(self, ...)
733 self.template = "cbi/value"
738 function Value.value(self, key, val)
740 table.insert(self.keylist, tostring(key))
741 table.insert(self.vallist, tostring(val))
745 -- DummyValue - This does nothing except being there
746 DummyValue = class(AbstractValue)
748 function DummyValue.__init__(self, map, ...)
749 AbstractValue.__init__(self, map, ...)
750 self.template = "cbi/dvalue"
754 function DummyValue.parse(self)
760 Flag - A flag being enabled or disabled
762 Flag = class(AbstractValue)
764 function Flag.__init__(self, ...)
765 AbstractValue.__init__(self, ...)
766 self.template = "cbi/fvalue"
772 -- A flag can only have two states: set or unset
773 function Flag.parse(self, section)
774 local fvalue = self:formvalue(section)
777 fvalue = self.enabled
779 fvalue = self.disabled
782 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
783 if not(fvalue == self:cfgvalue(section)) then
784 self:write(section, fvalue)
794 ListValue - A one-line value predefined in a list
795 widget: The widget that will be used (select, radio)
797 ListValue = class(AbstractValue)
799 function ListValue.__init__(self, ...)
800 AbstractValue.__init__(self, ...)
801 self.template = "cbi/lvalue"
806 self.widget = "select"
809 function ListValue.value(self, key, val)
811 table.insert(self.keylist, tostring(key))
812 table.insert(self.vallist, tostring(val))
815 function ListValue.validate(self, val)
816 if luci.util.contains(self.keylist, val) then
826 MultiValue - Multiple delimited values
827 widget: The widget that will be used (select, checkbox)
828 delimiter: The delimiter that will separate the values (default: " ")
830 MultiValue = class(AbstractValue)
832 function MultiValue.__init__(self, ...)
833 AbstractValue.__init__(self, ...)
834 self.template = "cbi/mvalue"
838 self.widget = "checkbox"
842 function MultiValue.render(self, ...)
843 if self.widget == "select" and not self.size then
844 self.size = #self.vallist
847 AbstractValue.render(self, ...)
850 function MultiValue.value(self, key, val)
852 table.insert(self.keylist, tostring(key))
853 table.insert(self.vallist, tostring(val))
856 function MultiValue.valuelist(self, section)
857 local val = self:cfgvalue(section)
859 if not(type(val) == "string") then
863 return luci.util.split(val, self.delimiter)
866 function MultiValue.validate(self, val)
867 val = (type(val) == "table") and val or {val}
871 for i, value in ipairs(val) do
872 if luci.util.contains(self.keylist, value) then
873 result = result and (result .. self.delimiter .. value) or value