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)
713 table.insert(self.deps, {field=field, value=value})
716 -- Generates the unique CBID
717 function AbstractValue.cbid(self, section)
718 return "cbid."..self.map.config.."."..section.."."..self.option
721 -- Return whether this object should be created
722 function AbstractValue.formcreated(self, section)
723 local key = "cbi.opt."..self.config.."."..section
724 return (luci.http.formvalue(key) == self.option)
727 -- Returns the formvalue for this object
728 function AbstractValue.formvalue(self, section)
729 return luci.http.formvalue(self:cbid(section))
732 function AbstractValue.additional(self, value)
733 self.optional = value
736 function AbstractValue.mandatory(self, value)
737 self.rmempty = not value
740 function AbstractValue.parse(self, section)
741 local fvalue = self:formvalue(section)
742 local cvalue = self:cfgvalue(section)
744 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
745 fvalue = self:transform(self:validate(fvalue, section))
747 self.tag_invalid[section] = true
749 if fvalue and not (fvalue == cvalue) then
750 self:write(section, fvalue)
752 else -- Unset the UCI or error
753 if self.rmempty or self.optional then
755 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
756 self.tag_missing[section] = true
761 -- Render if this value exists or if it is mandatory
762 function AbstractValue.render(self, s, scope)
763 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
766 scope.cbid = self:cbid(s)
768 scope.ifattr = function(cond,key,val)
770 return string.format(
771 ' %s="%s"', tostring(key),
772 luci.util.pcdata(tostring( val
774 or (type(self[key]) ~= "function" and self[key])
782 scope.attr = function(...)
783 return scope.ifattr( true, ... )
786 Node.render(self, scope)
790 -- Return the UCI value of this object
791 function AbstractValue.cfgvalue(self, section)
792 return self.map:get(section, self.option)
795 -- Validate the form value
796 function AbstractValue.validate(self, value)
800 AbstractValue.transform = AbstractValue.validate
804 function AbstractValue.write(self, section, value)
805 return self.map:set(section, self.option, value)
809 function AbstractValue.remove(self, section)
810 return self.map:del(section, self.option)
817 Value - A one-line value
818 maxlength: The maximum length
820 Value = class(AbstractValue)
822 function Value.__init__(self, ...)
823 AbstractValue.__init__(self, ...)
824 self.template = "cbi/value"
829 function Value.value(self, key, val)
831 table.insert(self.keylist, tostring(key))
832 table.insert(self.vallist, tostring(val))
836 -- DummyValue - This does nothing except being there
837 DummyValue = class(AbstractValue)
839 function DummyValue.__init__(self, map, ...)
840 AbstractValue.__init__(self, map, ...)
841 self.template = "cbi/dvalue"
845 function DummyValue.parse(self)
851 Flag - A flag being enabled or disabled
853 Flag = class(AbstractValue)
855 function Flag.__init__(self, ...)
856 AbstractValue.__init__(self, ...)
857 self.template = "cbi/fvalue"
863 -- A flag can only have two states: set or unset
864 function Flag.parse(self, section)
865 local fvalue = self:formvalue(section)
868 fvalue = self.enabled
870 fvalue = self.disabled
873 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
874 if not(fvalue == self:cfgvalue(section)) then
875 self:write(section, fvalue)
885 ListValue - A one-line value predefined in a list
886 widget: The widget that will be used (select, radio)
888 ListValue = class(AbstractValue)
890 function ListValue.__init__(self, ...)
891 AbstractValue.__init__(self, ...)
892 self.template = "cbi/lvalue"
897 self.widget = "select"
900 function ListValue.value(self, key, val)
902 table.insert(self.keylist, tostring(key))
903 table.insert(self.vallist, tostring(val))
906 function ListValue.validate(self, val)
907 if luci.util.contains(self.keylist, val) then
917 MultiValue - Multiple delimited values
918 widget: The widget that will be used (select, checkbox)
919 delimiter: The delimiter that will separate the values (default: " ")
921 MultiValue = class(AbstractValue)
923 function MultiValue.__init__(self, ...)
924 AbstractValue.__init__(self, ...)
925 self.template = "cbi/mvalue"
929 self.widget = "checkbox"
933 function MultiValue.render(self, ...)
934 if self.widget == "select" and not self.size then
935 self.size = #self.vallist
938 AbstractValue.render(self, ...)
941 function MultiValue.value(self, key, val)
943 table.insert(self.keylist, tostring(key))
944 table.insert(self.vallist, tostring(val))
947 function MultiValue.valuelist(self, section)
948 local val = self:cfgvalue(section)
950 if not(type(val) == "string") then
954 return luci.util.split(val, self.delimiter)
957 function MultiValue.validate(self, val)
958 val = (type(val) == "table") and val or {val}
962 for i, value in ipairs(val) do
963 if luci.util.contains(self.keylist, value) then
964 result = result and (result .. self.delimiter .. value) or value
972 TextValue - A multi-line value
975 TextValue = class(AbstractValue)
977 function TextValue.__init__(self, ...)
978 AbstractValue.__init__(self, ...)
979 self.template = "cbi/tvalue"
985 Button = class(AbstractValue)
987 function Button.__init__(self, ...)
988 AbstractValue.__init__(self, ...)
989 self.template = "cbi/button"
990 self.inputstyle = nil