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)
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 if self.stateful then
174 uci.load_state(self.config)
176 uci.load_config(self.config)
179 Node.parse(self, ...)
181 for i, config in ipairs(self.parsechain) do
182 uci.save_config(config)
184 if luci.http.formvalue("cbi.apply") then
185 for i, config in ipairs(self.parsechain) do
187 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
188 luci.util.exec(luci.config.uci_oncommit[config])
191 -- Refresh data because commit changes section names
192 uci.load_config(config)
196 Node.parse(self, ...)
199 for i, config in ipairs(self.parsechain) do
204 -- Creates a child section
205 function Map.section(self, class, ...)
206 if instanceof(class, AbstractSection) then
207 local obj = class(self, ...)
211 error("class must be a descendent of AbstractSection")
216 function Map.add(self, sectiontype)
217 return uci.add(self.config, sectiontype)
221 function Map.set(self, section, option, value)
223 return uci.set(self.config, section, option, value)
225 return uci.set(self.config, section, value)
230 function Map.del(self, section, option)
232 return uci.delete(self.config, section, option)
234 return uci.delete(self.config, section)
239 function Map.get(self, section, option)
241 return uci.get_all(self.config)
243 return uci.get(self.config, section, option)
245 return uci.get_all(self.config, section)
255 Page.__init__ = Node.__init__
256 Page.parse = function() end
260 SimpleForm - A Simple non-UCI form
262 SimpleForm = class(Node)
264 function SimpleForm.__init__(self, config, title, description, data)
265 Node.__init__(self, title, description)
267 self.data = data or {}
268 self.template = "cbi/simpleform"
272 function SimpleForm.parse(self, ...)
273 if luci.http.formvalue("cbi.submit") then
274 Node.parse(self, 1, ...)
278 for k, j in ipairs(self.children) do
279 for i, v in ipairs(j.children) do
281 and (not v.tag_missing or not v.tag_missing[1])
282 and (not v.tag_invalid or not v.tag_invalid[1])
287 not luci.http.formvalue("cbi.submit") and 0
291 self.dorender = not self.handle or self:handle(state, self.data) ~= false
294 function SimpleForm.render(self, ...)
295 if self.dorender then
296 Node.render(self, ...)
300 function SimpleForm.section(self, class, ...)
301 if instanceof(class, AbstractSection) then
302 local obj = class(self, ...)
306 error("class must be a descendent of AbstractSection")
310 -- Creates a child field
311 function SimpleForm.field(self, class, ...)
313 for k, v in ipairs(self.children) do
314 if instanceof(v, SimpleSection) then
320 section = self:section(SimpleSection)
323 if instanceof(class, AbstractValue) then
324 local obj = class(self, ...)
325 obj.track_missing = true
329 error("class must be a descendent of AbstractValue")
333 function SimpleForm.set(self, section, option, value)
334 self.data[option] = value
338 function SimpleForm.del(self, section, option)
339 self.data[option] = nil
343 function SimpleForm.get(self, section, option)
344 return self.data[option]
352 AbstractSection = class(Node)
354 function AbstractSection.__init__(self, map, sectiontype, ...)
355 Node.__init__(self, ...)
356 self.sectiontype = sectiontype
358 self.config = map.config
363 self.addremove = false
367 -- Appends a new option
368 function AbstractSection.option(self, class, option, ...)
369 if instanceof(class, AbstractValue) then
370 local obj = class(self.map, option, ...)
372 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
377 error("class must be a descendent of AbstractValue")
381 -- Parse optional options
382 function AbstractSection.parse_optionals(self, section)
383 if not self.optional then
387 self.optionals[section] = {}
389 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
390 for k,v in ipairs(self.children) do
391 if v.optional and not v:cfgvalue(section) then
392 if field == v.option then
395 table.insert(self.optionals[section], v)
400 if field and #field > 0 and self.dynamic then
401 self:add_dynamic(field)
405 -- Add a dynamic option
406 function AbstractSection.add_dynamic(self, field, optional)
407 local o = self:option(Value, field, field)
408 o.optional = optional
411 -- Parse all dynamic options
412 function AbstractSection.parse_dynamic(self, section)
413 if not self.dynamic then
417 local arr = luci.util.clone(self:cfgvalue(section))
418 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
419 for k, v in pairs(form) do
423 for key,val in pairs(arr) do
426 for i,c in ipairs(self.children) do
427 if c.option == key then
432 if create and key:sub(1, 1) ~= "." then
433 self:add_dynamic(key, true)
438 -- Returns the section's UCI table
439 function AbstractSection.cfgvalue(self, section)
440 return self.map:get(section)
443 -- Removes the section
444 function AbstractSection.remove(self, section)
445 return self.map:del(section)
448 -- Creates the section
449 function AbstractSection.create(self, section)
453 stat = self.map:set(section, nil, self.sectiontype)
455 section = self.map:add(self.sectiontype)
460 for k,v in pairs(self.children) do
462 self.map:set(section, v.option, v.default)
466 for k,v in pairs(self.defaults) do
467 self.map:set(section, k, v)
475 SimpleSection = class(AbstractSection)
477 function SimpleSection.__init__(self, form, ...)
478 AbstractSection.__init__(self, form, nil, ...)
479 self.template = "cbi/nullsection"
483 Table = class(AbstractSection)
485 function Table.__init__(self, form, data, ...)
486 local datasource = {}
487 datasource.config = "table"
490 function datasource.get(self, section, option)
491 return data[section] and data[section][option]
494 function datasource.del(...)
498 AbstractSection.__init__(self, datasource, "table", ...)
499 self.template = "cbi/tblsection"
500 self.rowcolors = true
501 self.anonymous = true
504 function Table.parse(self)
505 for i, k in ipairs(self:cfgsections()) do
506 if luci.http.formvalue("cbi.submit") then
512 function Table.cfgsections(self)
515 for i, v in luci.util.kspairs(self.data) do
516 table.insert(sections, i)
525 NamedSection - A fixed configuration section defined by its name
527 NamedSection = class(AbstractSection)
529 function NamedSection.__init__(self, map, section, type, ...)
530 AbstractSection.__init__(self, map, type, ...)
531 Node._i18n(self, map.config, section, nil, ...)
533 self.template = "cbi/nsection"
534 self.section = section
535 self.addremove = false
538 function NamedSection.parse(self)
539 local s = self.section
540 local active = self:cfgvalue(s)
543 if self.addremove then
544 local path = self.config.."."..s
545 if active then -- Remove the section
546 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
549 else -- Create and apply default values
550 if luci.http.formvalue("cbi.cns."..path) then
558 AbstractSection.parse_dynamic(self, s)
559 if luci.http.formvalue("cbi.submit") then
562 AbstractSection.parse_optionals(self, s)
568 TypedSection - A (set of) configuration section(s) defined by the type
569 addremove: Defines whether the user can add/remove sections of this type
570 anonymous: Allow creating anonymous sections
571 validate: a validation function returning nil if the section is invalid
573 TypedSection = class(AbstractSection)
575 function TypedSection.__init__(self, map, type, ...)
576 AbstractSection.__init__(self, map, type, ...)
577 Node._i18n(self, map.config, type, nil, ...)
579 self.template = "cbi/tsection"
582 self.anonymous = false
585 -- Return all matching UCI sections for this TypedSection
586 function TypedSection.cfgsections(self)
588 uci.foreach(self.map.config, self.sectiontype,
590 if self:checkscope(section[".name"]) then
591 table.insert(sections, section[".name"])
598 -- Limits scope to sections that have certain option => value pairs
599 function TypedSection.depends(self, option, value)
600 table.insert(self.deps, {option=option, value=value})
603 function TypedSection.parse(self)
604 if self.addremove then
606 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
607 local name = luci.http.formvalue(crval)
608 if self.anonymous then
614 -- Ignore if it already exists
615 if self:cfgvalue(name) then
619 name = self:checkscope(name)
622 self.err_invalid = true
625 if name and name:len() > 0 then
632 crval = REMOVE_PREFIX .. self.config
633 name = luci.http.formvaluetable(crval)
634 for k,v in pairs(name) do
635 if self:cfgvalue(k) and self:checkscope(k) then
641 for i, k in ipairs(self:cfgsections()) do
642 AbstractSection.parse_dynamic(self, k)
643 if luci.http.formvalue("cbi.submit") then
646 AbstractSection.parse_optionals(self, k)
650 -- Verifies scope of sections
651 function TypedSection.checkscope(self, section)
652 -- Check if we are not excluded
653 if self.filter and not self:filter(section) then
657 -- Check if at least one dependency is met
658 if #self.deps > 0 and self:cfgvalue(section) then
661 for k, v in ipairs(self.deps) do
662 if self:cfgvalue(section)[v.option] == v.value then
672 return self:validate(section)
676 -- Dummy validate function
677 function TypedSection.validate(self, section)
683 AbstractValue - An abstract Value Type
684 null: Value can be empty
685 valid: A function returning the value if it is valid otherwise nil
686 depends: A table of option => value pairs of which one must be true
687 default: The default value
688 size: The size of the input fields
689 rmempty: Unset value if empty
690 optional: This value is optional (see AbstractSection.optionals)
692 AbstractValue = class(Node)
694 function AbstractValue.__init__(self, map, option, ...)
695 Node.__init__(self, ...)
698 self.config = map.config
699 self.tag_invalid = {}
700 self.tag_missing = {}
704 self.track_missing = false
708 self.optional = false
711 -- Add a dependencie to another section field
712 function AbstractValue.depends(self, field, value)
714 if type(field) == "string" then
721 table.insert(self.deps, {deps=deps, add=""})
724 -- Generates the unique CBID
725 function AbstractValue.cbid(self, section)
726 return "cbid."..self.map.config.."."..section.."."..self.option
729 -- Return whether this object should be created
730 function AbstractValue.formcreated(self, section)
731 local key = "cbi.opt."..self.config.."."..section
732 return (luci.http.formvalue(key) == self.option)
735 -- Returns the formvalue for this object
736 function AbstractValue.formvalue(self, section)
737 return luci.http.formvalue(self:cbid(section))
740 function AbstractValue.additional(self, value)
741 self.optional = value
744 function AbstractValue.mandatory(self, value)
745 self.rmempty = not value
748 function AbstractValue.parse(self, section)
749 local fvalue = self:formvalue(section)
750 local cvalue = self:cfgvalue(section)
752 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
753 fvalue = self:transform(self:validate(fvalue, section))
755 self.tag_invalid[section] = true
757 if fvalue and not (fvalue == cvalue) then
758 self:write(section, fvalue)
760 else -- Unset the UCI or error
761 if self.rmempty or self.optional then
763 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
764 self.tag_missing[section] = true
769 -- Render if this value exists or if it is mandatory
770 function AbstractValue.render(self, s, scope)
771 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
774 scope.cbid = self:cbid(s)
776 scope.ifattr = function(cond,key,val)
778 return string.format(
779 ' %s="%s"', tostring(key),
780 luci.util.pcdata(tostring( val
782 or (type(self[key]) ~= "function" and self[key])
790 scope.attr = function(...)
791 return scope.ifattr( true, ... )
794 Node.render(self, scope)
798 -- Return the UCI value of this object
799 function AbstractValue.cfgvalue(self, section)
800 return self.map:get(section, self.option)
803 -- Validate the form value
804 function AbstractValue.validate(self, value)
808 AbstractValue.transform = AbstractValue.validate
812 function AbstractValue.write(self, section, value)
813 return self.map:set(section, self.option, value)
817 function AbstractValue.remove(self, section)
818 return self.map:del(section, self.option)
825 Value - A one-line value
826 maxlength: The maximum length
828 Value = class(AbstractValue)
830 function Value.__init__(self, ...)
831 AbstractValue.__init__(self, ...)
832 self.template = "cbi/value"
837 function Value.value(self, key, val)
839 table.insert(self.keylist, tostring(key))
840 table.insert(self.vallist, tostring(val))
844 -- DummyValue - This does nothing except being there
845 DummyValue = class(AbstractValue)
847 function DummyValue.__init__(self, map, ...)
848 AbstractValue.__init__(self, map, ...)
849 self.template = "cbi/dvalue"
853 function DummyValue.parse(self)
859 Flag - A flag being enabled or disabled
861 Flag = class(AbstractValue)
863 function Flag.__init__(self, ...)
864 AbstractValue.__init__(self, ...)
865 self.template = "cbi/fvalue"
871 -- A flag can only have two states: set or unset
872 function Flag.parse(self, section)
873 local fvalue = self:formvalue(section)
876 fvalue = self.enabled
878 fvalue = self.disabled
881 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
882 if not(fvalue == self:cfgvalue(section)) then
883 self:write(section, fvalue)
893 ListValue - A one-line value predefined in a list
894 widget: The widget that will be used (select, radio)
896 ListValue = class(AbstractValue)
898 function ListValue.__init__(self, ...)
899 AbstractValue.__init__(self, ...)
900 self.template = "cbi/lvalue"
905 self.widget = "select"
908 function ListValue.value(self, key, val, ...)
910 table.insert(self.keylist, tostring(key))
911 table.insert(self.vallist, tostring(val))
913 for i, deps in ipairs({...}) do
914 table.insert(self.deps, {add = "-"..key, deps=deps})
918 function ListValue.validate(self, val)
919 if luci.util.contains(self.keylist, val) then
929 MultiValue - Multiple delimited values
930 widget: The widget that will be used (select, checkbox)
931 delimiter: The delimiter that will separate the values (default: " ")
933 MultiValue = class(AbstractValue)
935 function MultiValue.__init__(self, ...)
936 AbstractValue.__init__(self, ...)
937 self.template = "cbi/mvalue"
941 self.widget = "checkbox"
945 function MultiValue.render(self, ...)
946 if self.widget == "select" and not self.size then
947 self.size = #self.vallist
950 AbstractValue.render(self, ...)
953 function MultiValue.value(self, key, val)
955 table.insert(self.keylist, tostring(key))
956 table.insert(self.vallist, tostring(val))
959 function MultiValue.valuelist(self, section)
960 local val = self:cfgvalue(section)
962 if not(type(val) == "string") then
966 return luci.util.split(val, self.delimiter)
969 function MultiValue.validate(self, val)
970 val = (type(val) == "table") and val or {val}
974 for i, value in ipairs(val) do
975 if luci.util.contains(self.keylist, value) then
976 result = result and (result .. self.delimiter .. value) or value
984 TextValue - A multi-line value
987 TextValue = class(AbstractValue)
989 function TextValue.__init__(self, ...)
990 AbstractValue.__init__(self, ...)
991 self.template = "cbi/tvalue"
997 Button = class(AbstractValue)
999 function Button.__init__(self, ...)
1000 AbstractValue.__init__(self, ...)
1001 self.template = "cbi/button"
1002 self.inputstyle = nil