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 and config:gsub("[^%w]+", "") or ""
97 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
98 if option then key = key .. "_" .. tostring(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_config(self.config) then
161 error("Unable to read UCI data: " .. self.config)
165 function Map.render(self, ...)
166 if self.stateful then
167 uci.load_state(self.config)
169 uci.load_config(self.config)
171 Node.render(self, ...)
175 -- Chain foreign config
176 function Map.chain(self, config)
177 table.insert(self.parsechain, config)
180 -- Use optimized UCI writing
181 function Map.parse(self, ...)
182 if self.stateful then
183 uci.load_state(self.config)
185 uci.load_config(self.config)
188 Node.parse(self, ...)
190 for i, config in ipairs(self.parsechain) do
191 uci.save_config(config)
193 if luci.http.formvalue("cbi.apply") then
194 for i, config in ipairs(self.parsechain) do
196 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
197 luci.util.exec(luci.config.uci_oncommit[config])
200 -- Refresh data because commit changes section names
201 uci.load_config(config)
205 Node.parse(self, ...)
208 for i, config in ipairs(self.parsechain) do
213 -- Creates a child section
214 function Map.section(self, class, ...)
215 if instanceof(class, AbstractSection) then
216 local obj = class(self, ...)
220 error("class must be a descendent of AbstractSection")
225 function Map.add(self, sectiontype)
226 return uci.add(self.config, sectiontype)
230 function Map.set(self, section, option, value)
232 return uci.set(self.config, section, option, value)
234 return uci.set(self.config, section, value)
239 function Map.del(self, section, option)
241 return uci.delete(self.config, section, option)
243 return uci.delete(self.config, section)
248 function Map.get(self, section, option)
250 return uci.get_all(self.config)
252 return uci.get(self.config, section, option)
254 return uci.get_all(self.config, section)
264 Page.__init__ = Node.__init__
265 Page.parse = function() end
269 SimpleForm - A Simple non-UCI form
271 SimpleForm = class(Node)
273 function SimpleForm.__init__(self, config, title, description, data)
274 Node.__init__(self, title, description)
276 self.data = data or {}
277 self.template = "cbi/simpleform"
281 function SimpleForm.parse(self, ...)
282 if luci.http.formvalue("cbi.submit") then
283 Node.parse(self, 1, ...)
287 for k, j in ipairs(self.children) do
288 for i, v in ipairs(j.children) do
290 and (not v.tag_missing or not v.tag_missing[1])
291 and (not v.tag_invalid or not v.tag_invalid[1])
296 not luci.http.formvalue("cbi.submit") and 0
300 self.dorender = not self.handle or self:handle(state, self.data) ~= false
303 function SimpleForm.render(self, ...)
304 if self.dorender then
305 Node.render(self, ...)
309 function SimpleForm.section(self, class, ...)
310 if instanceof(class, AbstractSection) then
311 local obj = class(self, ...)
315 error("class must be a descendent of AbstractSection")
319 -- Creates a child field
320 function SimpleForm.field(self, class, ...)
322 for k, v in ipairs(self.children) do
323 if instanceof(v, SimpleSection) then
329 section = self:section(SimpleSection)
332 if instanceof(class, AbstractValue) then
333 local obj = class(self, ...)
334 obj.track_missing = true
338 error("class must be a descendent of AbstractValue")
342 function SimpleForm.set(self, section, option, value)
343 self.data[option] = value
347 function SimpleForm.del(self, section, option)
348 self.data[option] = nil
352 function SimpleForm.get(self, section, option)
353 return self.data[option]
361 AbstractSection = class(Node)
363 function AbstractSection.__init__(self, map, sectiontype, ...)
364 Node.__init__(self, ...)
365 self.sectiontype = sectiontype
367 self.config = map.config
373 self.addremove = false
377 -- Appends a new option
378 function AbstractSection.option(self, class, option, ...)
379 if instanceof(class, AbstractValue) then
380 local obj = class(self.map, option, ...)
382 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
387 error("class must be a descendent of AbstractValue")
391 -- Parse optional options
392 function AbstractSection.parse_optionals(self, section)
393 if not self.optional then
397 self.optionals[section] = {}
399 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
400 for k,v in ipairs(self.children) do
401 if v.optional and not v:cfgvalue(section) then
402 if field == v.option then
405 table.insert(self.optionals[section], v)
410 if field and #field > 0 and self.dynamic then
411 self:add_dynamic(field)
415 -- Add a dynamic option
416 function AbstractSection.add_dynamic(self, field, optional)
417 local o = self:option(Value, field, field)
418 o.optional = optional
421 -- Parse all dynamic options
422 function AbstractSection.parse_dynamic(self, section)
423 if not self.dynamic then
427 local arr = luci.util.clone(self:cfgvalue(section))
428 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
429 for k, v in pairs(form) do
433 for key,val in pairs(arr) do
436 for i,c in ipairs(self.children) do
437 if c.option == key then
442 if create and key:sub(1, 1) ~= "." then
443 self:add_dynamic(key, true)
448 -- Returns the section's UCI table
449 function AbstractSection.cfgvalue(self, section)
450 local value = self.map:get(section)
451 if not self.cast or self.cast == type(value) then
453 elseif self.cast == "string" then
454 if type(value) == "table" then
457 elseif self.cast == "table" then
462 -- Removes the section
463 function AbstractSection.remove(self, section)
464 return self.map:del(section)
467 -- Creates the section
468 function AbstractSection.create(self, section)
472 stat = self.map:set(section, nil, self.sectiontype)
474 section = self.map:add(self.sectiontype)
479 for k,v in pairs(self.children) do
481 self.map:set(section, v.option, v.default)
485 for k,v in pairs(self.defaults) do
486 self.map:set(section, k, v)
494 SimpleSection = class(AbstractSection)
496 function SimpleSection.__init__(self, form, ...)
497 AbstractSection.__init__(self, form, nil, ...)
498 self.template = "cbi/nullsection"
502 Table = class(AbstractSection)
504 function Table.__init__(self, form, data, ...)
505 local datasource = {}
506 datasource.config = "table"
509 function datasource.get(self, section, option)
510 return data[section] and data[section][option]
513 function datasource.del(...)
517 AbstractSection.__init__(self, datasource, "table", ...)
518 self.template = "cbi/tblsection"
519 self.rowcolors = true
520 self.anonymous = true
523 function Table.parse(self)
524 for i, k in ipairs(self:cfgsections()) do
525 if luci.http.formvalue("cbi.submit") then
531 function Table.cfgsections(self)
534 for i, v in luci.util.kspairs(self.data) do
535 table.insert(sections, i)
544 NamedSection - A fixed configuration section defined by its name
546 NamedSection = class(AbstractSection)
548 function NamedSection.__init__(self, map, section, type, ...)
549 AbstractSection.__init__(self, map, type, ...)
550 Node._i18n(self, map.config, section, nil, ...)
552 self.template = "cbi/nsection"
553 self.section = section
554 self.addremove = false
557 function NamedSection.parse(self)
558 local s = self.section
559 local active = self:cfgvalue(s)
562 if self.addremove then
563 local path = self.config.."."..s
564 if active then -- Remove the section
565 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
568 else -- Create and apply default values
569 if luci.http.formvalue("cbi.cns."..path) then
577 AbstractSection.parse_dynamic(self, s)
578 if luci.http.formvalue("cbi.submit") then
581 AbstractSection.parse_optionals(self, s)
587 TypedSection - A (set of) configuration section(s) defined by the type
588 addremove: Defines whether the user can add/remove sections of this type
589 anonymous: Allow creating anonymous sections
590 validate: a validation function returning nil if the section is invalid
592 TypedSection = class(AbstractSection)
594 function TypedSection.__init__(self, map, type, ...)
595 AbstractSection.__init__(self, map, type, ...)
596 Node._i18n(self, map.config, type, nil, ...)
598 self.template = "cbi/tsection"
601 self.anonymous = false
604 -- Return all matching UCI sections for this TypedSection
605 function TypedSection.cfgsections(self)
607 uci.foreach(self.map.config, self.sectiontype,
609 if self:checkscope(section[".name"]) then
610 table.insert(sections, section[".name"])
617 -- Limits scope to sections that have certain option => value pairs
618 function TypedSection.depends(self, option, value)
619 table.insert(self.deps, {option=option, value=value})
622 function TypedSection.parse(self)
623 if self.addremove then
625 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
626 local name = luci.http.formvalue(crval)
627 if self.anonymous then
633 -- Ignore if it already exists
634 if self:cfgvalue(name) then
638 name = self:checkscope(name)
641 self.err_invalid = true
644 if name and name:len() > 0 then
651 crval = REMOVE_PREFIX .. self.config
652 name = luci.http.formvaluetable(crval)
653 for k,v in pairs(name) do
654 if self:cfgvalue(k) and self:checkscope(k) then
660 for i, k in ipairs(self:cfgsections()) do
661 AbstractSection.parse_dynamic(self, k)
662 if luci.http.formvalue("cbi.submit") then
665 AbstractSection.parse_optionals(self, k)
669 -- Verifies scope of sections
670 function TypedSection.checkscope(self, section)
671 -- Check if we are not excluded
672 if self.filter and not self:filter(section) then
676 -- Check if at least one dependency is met
677 if #self.deps > 0 and self:cfgvalue(section) then
680 for k, v in ipairs(self.deps) do
681 if self:cfgvalue(section)[v.option] == v.value then
691 return self:validate(section)
695 -- Dummy validate function
696 function TypedSection.validate(self, section)
702 AbstractValue - An abstract Value Type
703 null: Value can be empty
704 valid: A function returning the value if it is valid otherwise nil
705 depends: A table of option => value pairs of which one must be true
706 default: The default value
707 size: The size of the input fields
708 rmempty: Unset value if empty
709 optional: This value is optional (see AbstractSection.optionals)
711 AbstractValue = class(Node)
713 function AbstractValue.__init__(self, map, option, ...)
714 Node.__init__(self, ...)
717 self.config = map.config
718 self.tag_invalid = {}
719 self.tag_missing = {}
723 self.track_missing = false
727 self.optional = false
730 -- Add a dependencie to another section field
731 function AbstractValue.depends(self, field, value)
733 if type(field) == "string" then
740 table.insert(self.deps, {deps=deps, add=""})
743 -- Generates the unique CBID
744 function AbstractValue.cbid(self, section)
745 return "cbid."..self.map.config.."."..section.."."..self.option
748 -- Return whether this object should be created
749 function AbstractValue.formcreated(self, section)
750 local key = "cbi.opt."..self.config.."."..section
751 return (luci.http.formvalue(key) == self.option)
754 -- Returns the formvalue for this object
755 function AbstractValue.formvalue(self, section)
756 return luci.http.formvalue(self:cbid(section))
759 function AbstractValue.additional(self, value)
760 self.optional = value
763 function AbstractValue.mandatory(self, value)
764 self.rmempty = not value
767 function AbstractValue.parse(self, section)
768 local fvalue = self:formvalue(section)
769 local cvalue = self:cfgvalue(section)
771 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
772 fvalue = self:transform(self:validate(fvalue, section))
774 self.tag_invalid[section] = true
776 if fvalue and not (fvalue == cvalue) then
777 self:write(section, fvalue)
779 else -- Unset the UCI or error
780 if self.rmempty or self.optional then
782 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
783 self.tag_missing[section] = true
788 -- Render if this value exists or if it is mandatory
789 function AbstractValue.render(self, s, scope)
790 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
793 scope.cbid = self:cbid(s)
795 scope.ifattr = function(cond,key,val)
797 return string.format(
798 ' %s="%s"', tostring(key),
799 luci.util.pcdata(tostring( val
801 or (type(self[key]) ~= "function" and self[key])
809 scope.attr = function(...)
810 return scope.ifattr( true, ... )
813 Node.render(self, scope)
817 -- Return the UCI value of this object
818 function AbstractValue.cfgvalue(self, section)
819 return self.map:get(section, self.option)
822 -- Validate the form value
823 function AbstractValue.validate(self, value)
827 AbstractValue.transform = AbstractValue.validate
831 function AbstractValue.write(self, section, value)
832 return self.map:set(section, self.option, value)
836 function AbstractValue.remove(self, section)
837 return self.map:del(section, self.option)
844 Value - A one-line value
845 maxlength: The maximum length
847 Value = class(AbstractValue)
849 function Value.__init__(self, ...)
850 AbstractValue.__init__(self, ...)
851 self.template = "cbi/value"
856 function Value.value(self, key, val)
858 table.insert(self.keylist, tostring(key))
859 table.insert(self.vallist, tostring(val))
863 -- DummyValue - This does nothing except being there
864 DummyValue = class(AbstractValue)
866 function DummyValue.__init__(self, map, ...)
867 AbstractValue.__init__(self, map, ...)
868 self.template = "cbi/dvalue"
872 function DummyValue.parse(self)
878 Flag - A flag being enabled or disabled
880 Flag = class(AbstractValue)
882 function Flag.__init__(self, ...)
883 AbstractValue.__init__(self, ...)
884 self.template = "cbi/fvalue"
890 -- A flag can only have two states: set or unset
891 function Flag.parse(self, section)
892 local fvalue = self:formvalue(section)
895 fvalue = self.enabled
897 fvalue = self.disabled
900 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
901 if not(fvalue == self:cfgvalue(section)) then
902 self:write(section, fvalue)
912 ListValue - A one-line value predefined in a list
913 widget: The widget that will be used (select, radio)
915 ListValue = class(AbstractValue)
917 function ListValue.__init__(self, ...)
918 AbstractValue.__init__(self, ...)
919 self.template = "cbi/lvalue"
924 self.widget = "select"
927 function ListValue.value(self, key, val, ...)
929 table.insert(self.keylist, tostring(key))
930 table.insert(self.vallist, tostring(val))
932 for i, deps in ipairs({...}) do
933 table.insert(self.deps, {add = "-"..key, deps=deps})
937 function ListValue.validate(self, val)
938 if luci.util.contains(self.keylist, val) then
948 MultiValue - Multiple delimited values
949 widget: The widget that will be used (select, checkbox)
950 delimiter: The delimiter that will separate the values (default: " ")
952 MultiValue = class(AbstractValue)
954 function MultiValue.__init__(self, ...)
955 AbstractValue.__init__(self, ...)
956 self.template = "cbi/mvalue"
960 self.widget = "checkbox"
964 function MultiValue.render(self, ...)
965 if self.widget == "select" and not self.size then
966 self.size = #self.vallist
969 AbstractValue.render(self, ...)
972 function MultiValue.value(self, key, val)
974 table.insert(self.keylist, tostring(key))
975 table.insert(self.vallist, tostring(val))
978 function MultiValue.valuelist(self, section)
979 local val = self:cfgvalue(section)
981 if not(type(val) == "string") then
985 return luci.util.split(val, self.delimiter)
988 function MultiValue.validate(self, val)
989 val = (type(val) == "table") and val or {val}
993 for i, value in ipairs(val) do
994 if luci.util.contains(self.keylist, value) then
995 result = result and (result .. self.delimiter .. value) or value
1003 StaticList = class(MultiValue)
1005 function StaticList.__init__(self, ...)
1006 MultiValue.__init__(self, ...)
1008 self.valuelist = self.cfgvalue
1011 function StaticList.validate(self, value)
1012 value = (type(value) == "table") and value or {value}
1015 for i, v in ipairs(value) do
1016 if luci.util.contains(self.valuelist, v) then
1017 table.insert(valid, v)
1024 DynamicList = class(AbstractValue)
1026 function DynamicList.__init__(self, ...)
1027 AbstractValue.__init__(self, ...)
1028 self.template = "cbi/dynlist"
1035 function DynamicList.value(self, key, val)
1037 table.insert(self.keylist, tostring(key))
1038 table.insert(self.vallist, tostring(val))
1041 function DynamicList.validate(self, value, section)
1042 value = (type(value) == "table") and value or {value}
1045 for i, v in ipairs(value) do
1047 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1048 table.insert(valid, v)
1057 TextValue - A multi-line value
1060 TextValue = class(AbstractValue)
1062 function TextValue.__init__(self, ...)
1063 AbstractValue.__init__(self, ...)
1064 self.template = "cbi/tvalue"
1070 Button = class(AbstractValue)
1072 function Button.__init__(self, ...)
1073 AbstractValue.__init__(self, ...)
1074 self.template = "cbi/button"
1075 self.inputstyle = nil