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")
35 local uci = luci.model.uci
36 local class = luci.util.class
37 local instanceof = luci.util.instanceof
45 CREATE_PREFIX = "cbi.cts."
46 REMOVE_PREFIX = "cbi.rts."
48 -- Loads a CBI map from given file, creating an environment and returns it
49 function load(cbimap, ...)
52 require("luci.config")
55 local cbidir = luci.util.libpath() .. "/model/cbi/"
56 local func, err = loadfile(cbidir..cbimap..".lua")
62 luci.i18n.loadc("cbi")
64 luci.util.resfenv(func)
65 luci.util.updfenv(func, luci.cbi)
66 luci.util.extfenv(func, "translate", luci.i18n.translate)
67 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
68 luci.util.extfenv(func, "arg", {...})
72 for i, map in ipairs(maps) do
73 if not instanceof(map, Node) then
74 error("CBI map returns no valid map object!")
83 function _uvl_strip_remote_dependencies(deps)
86 for k, v in pairs(deps) do
87 k = k:gsub("%$config%.%$section%.", "")
88 if k:match("^[%w_]+$") and type(v) == "string" then
97 -- Node pseudo abstract class
100 function Node.__init__(self, title, description)
102 self.title = title or ""
103 self.description = description or ""
104 self.template = "cbi/node"
108 function Node._i18n(self, config, section, option, title, description)
111 if type(luci.i18n) == "table" then
113 local key = config and config:gsub("[^%w]+", "") or ""
115 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
116 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
118 self.title = title or luci.i18n.translate( key, option or section or config )
119 self.description = description or luci.i18n.translate( key .. "_desc", "" )
123 -- Append child nodes
124 function Node.append(self, obj)
125 table.insert(self.children, obj)
128 -- Parse this node and its children
129 function Node.parse(self, ...)
130 for k, child in ipairs(self.children) do
136 function Node.render(self, scope)
140 luci.template.render(self.template, scope)
143 -- Render the children
144 function Node.render_children(self, ...)
145 for k, node in ipairs(self.children) do
152 A simple template element
154 Template = class(Node)
156 function Template.__init__(self, template)
158 self.template = template
161 function Template.render(self)
162 luci.template.render(self.template, {self=self})
167 Map - A map describing a configuration file
171 function Map.__init__(self, config, ...)
172 Node.__init__(self, ...)
173 Node._i18n(self, config, nil, nil, ...)
176 self.parsechain = {self.config}
177 self.template = "cbi/map"
178 if not uci.load_config(self.config) then
179 error("Unable to read UCI data: " .. self.config)
182 self.validator = luci.uvl.UVL()
183 self.scheme = self.validator:get_scheme(self.config)
186 function Map.get_scheme(self, sectiontype, option)
188 return self.scheme and self.scheme.sections[sectiontype]
190 return self.scheme and self.scheme.variables[sectiontype]
191 and self.scheme.variables[sectiontype][option]
195 function Map.render(self, ...)
196 if self.stateful then
197 uci.load_state(self.config)
199 uci.load_config(self.config)
201 Node.render(self, ...)
205 -- Chain foreign config
206 function Map.chain(self, config)
207 table.insert(self.parsechain, config)
210 -- Use optimized UCI writing
211 function Map.parse(self, ...)
212 if self.stateful then
213 uci.load_state(self.config)
215 uci.load_config(self.config)
218 Node.parse(self, ...)
220 for i, config in ipairs(self.parsechain) do
221 uci.save_config(config)
223 if luci.http.formvalue("cbi.apply") then
224 for i, config in ipairs(self.parsechain) do
226 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
227 luci.util.exec(luci.config.uci_oncommit[config])
230 -- Refresh data because commit changes section names
231 uci.load_config(config)
235 Node.parse(self, ...)
238 for i, config in ipairs(self.parsechain) do
243 -- Creates a child section
244 function Map.section(self, class, ...)
245 if instanceof(class, AbstractSection) then
246 local obj = class(self, ...)
250 error("class must be a descendent of AbstractSection")
255 function Map.add(self, sectiontype)
256 return uci.add(self.config, sectiontype)
260 function Map.set(self, section, option, value)
262 return uci.set(self.config, section, option, value)
264 return uci.set(self.config, section, value)
269 function Map.del(self, section, option)
271 return uci.delete(self.config, section, option)
273 return uci.delete(self.config, section)
278 function Map.get(self, section, option)
280 return uci.get_all(self.config)
282 return uci.get(self.config, section, option)
284 return uci.get_all(self.config, section)
294 Page.__init__ = Node.__init__
295 Page.parse = function() end
299 SimpleForm - A Simple non-UCI form
301 SimpleForm = class(Node)
303 function SimpleForm.__init__(self, config, title, description, data)
304 Node.__init__(self, title, description)
306 self.data = data or {}
307 self.template = "cbi/simpleform"
311 function SimpleForm.parse(self, ...)
312 if luci.http.formvalue("cbi.submit") then
313 Node.parse(self, 1, ...)
317 for k, j in ipairs(self.children) do
318 for i, v in ipairs(j.children) do
320 and (not v.tag_missing or not v.tag_missing[1])
321 and (not v.tag_invalid or not v.tag_invalid[1])
326 not luci.http.formvalue("cbi.submit") and 0
330 self.dorender = not self.handle or self:handle(state, self.data) ~= false
333 function SimpleForm.render(self, ...)
334 if self.dorender then
335 Node.render(self, ...)
339 function SimpleForm.section(self, class, ...)
340 if instanceof(class, AbstractSection) then
341 local obj = class(self, ...)
345 error("class must be a descendent of AbstractSection")
349 -- Creates a child field
350 function SimpleForm.field(self, class, ...)
352 for k, v in ipairs(self.children) do
353 if instanceof(v, SimpleSection) then
359 section = self:section(SimpleSection)
362 if instanceof(class, AbstractValue) then
363 local obj = class(self, ...)
364 obj.track_missing = true
368 error("class must be a descendent of AbstractValue")
372 function SimpleForm.set(self, section, option, value)
373 self.data[option] = value
377 function SimpleForm.del(self, section, option)
378 self.data[option] = nil
382 function SimpleForm.get(self, section, option)
383 return self.data[option]
387 function SimpleForm.get_scheme()
396 AbstractSection = class(Node)
398 function AbstractSection.__init__(self, map, sectiontype, ...)
399 Node.__init__(self, ...)
400 self.sectiontype = sectiontype
402 self.config = map.config
407 self.addremove = false
411 -- Appends a new option
412 function AbstractSection.option(self, class, option, ...)
413 -- Autodetect from UVL
414 if class == true and self.map:get_scheme(self.sectiontype, option) then
415 local vs = self.map:get_scheme(self.sectiontype, option)
416 if vs.type == "boolean" then
418 elseif vs.type == "list" then
420 elseif vs.type == "enum" or vs.type == "reference" then
427 if instanceof(class, AbstractValue) then
428 local obj = class(self.map, self, option, ...)
430 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
434 elseif class == true then
435 error("No valid class was given and autodetection failed.")
437 error("class must be a descendant of AbstractValue")
441 -- Parse optional options
442 function AbstractSection.parse_optionals(self, section)
443 if not self.optional then
447 self.optionals[section] = {}
449 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
450 for k,v in ipairs(self.children) do
451 if v.optional and not v:cfgvalue(section) then
452 if field == v.option then
455 table.insert(self.optionals[section], v)
460 if field and #field > 0 and self.dynamic then
461 self:add_dynamic(field)
465 -- Add a dynamic option
466 function AbstractSection.add_dynamic(self, field, optional)
467 local o = self:option(Value, field, field)
468 o.optional = optional
471 -- Parse all dynamic options
472 function AbstractSection.parse_dynamic(self, section)
473 if not self.dynamic then
477 local arr = luci.util.clone(self:cfgvalue(section))
478 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
479 for k, v in pairs(form) do
483 for key,val in pairs(arr) do
486 for i,c in ipairs(self.children) do
487 if c.option == key then
492 if create and key:sub(1, 1) ~= "." then
493 self:add_dynamic(key, true)
498 -- Returns the section's UCI table
499 function AbstractSection.cfgvalue(self, section)
500 return self.map:get(section)
503 -- Removes the section
504 function AbstractSection.remove(self, section)
505 return self.map:del(section)
508 -- Creates the section
509 function AbstractSection.create(self, section)
513 stat = self.map:set(section, nil, self.sectiontype)
515 section = self.map:add(self.sectiontype)
520 for k,v in pairs(self.children) do
522 self.map:set(section, v.option, v.default)
526 for k,v in pairs(self.defaults) do
527 self.map:set(section, k, v)
535 SimpleSection = class(AbstractSection)
537 function SimpleSection.__init__(self, form, ...)
538 AbstractSection.__init__(self, form, nil, ...)
539 self.template = "cbi/nullsection"
543 Table = class(AbstractSection)
545 function Table.__init__(self, form, data, ...)
546 local datasource = {}
547 datasource.config = "table"
550 function datasource.get(self, section, option)
551 return data[section] and data[section][option]
554 function datasource.del(...)
558 function datasource.get_scheme()
562 AbstractSection.__init__(self, datasource, "table", ...)
563 self.template = "cbi/tblsection"
564 self.rowcolors = true
565 self.anonymous = true
568 function Table.parse(self)
569 for i, k in ipairs(self:cfgsections()) do
570 if luci.http.formvalue("cbi.submit") then
576 function Table.cfgsections(self)
579 for i, v in luci.util.kspairs(self.data) do
580 table.insert(sections, i)
589 NamedSection - A fixed configuration section defined by its name
591 NamedSection = class(AbstractSection)
593 function NamedSection.__init__(self, map, section, stype, ...)
594 AbstractSection.__init__(self, map, stype, ...)
595 Node._i18n(self, map.config, section, nil, ...)
598 self.addremove = false
600 -- Use defaults from UVL
601 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
602 local vs = self.map:get_scheme(self.sectiontype)
603 self.addremove = not vs.unique and not vs.required
604 self.dynamic = vs.dynamic
605 self.title = self.title or vs.title
606 self.description = self.description or vs.descr
609 self.template = "cbi/nsection"
610 self.section = section
613 function NamedSection.parse(self)
614 local s = self.section
615 local active = self:cfgvalue(s)
618 if self.addremove then
619 local path = self.config.."."..s
620 if active then -- Remove the section
621 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
624 else -- Create and apply default values
625 if luci.http.formvalue("cbi.cns."..path) then
633 AbstractSection.parse_dynamic(self, s)
634 if luci.http.formvalue("cbi.submit") then
637 AbstractSection.parse_optionals(self, s)
643 TypedSection - A (set of) configuration section(s) defined by the type
644 addremove: Defines whether the user can add/remove sections of this type
645 anonymous: Allow creating anonymous sections
646 validate: a validation function returning nil if the section is invalid
648 TypedSection = class(AbstractSection)
650 function TypedSection.__init__(self, map, type, ...)
651 AbstractSection.__init__(self, map, type, ...)
652 Node._i18n(self, map.config, type, nil, ...)
654 self.template = "cbi/tsection"
656 self.anonymous = false
658 -- Use defaults from UVL
659 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
660 local vs = self.map:get_scheme(self.sectiontype)
661 self.addremove = not vs.unique and not vs.required
662 self.dynamic = vs.dynamic
663 self.anonymous = not vs.named
664 self.title = self.title or vs.title
665 self.description = self.description or vs.descr
669 -- Return all matching UCI sections for this TypedSection
670 function TypedSection.cfgsections(self)
672 uci.foreach(self.map.config, self.sectiontype,
674 if self:checkscope(section[".name"]) then
675 table.insert(sections, section[".name"])
682 -- Limits scope to sections that have certain option => value pairs
683 function TypedSection.depends(self, option, value)
684 table.insert(self.deps, {option=option, value=value})
687 function TypedSection.parse(self)
688 if self.addremove then
690 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
691 local name = luci.http.formvalue(crval)
692 if self.anonymous then
698 -- Ignore if it already exists
699 if self:cfgvalue(name) then
703 name = self:checkscope(name)
706 self.err_invalid = true
709 if name and name:len() > 0 then
716 crval = REMOVE_PREFIX .. self.config
717 name = luci.http.formvaluetable(crval)
718 for k,v in pairs(name) do
720 luci.util.perror(self:cfgvalue(k))
721 luci.util.perror(self:checkscope(k))
722 if self:cfgvalue(k) and self:checkscope(k) then
728 for i, k in ipairs(self:cfgsections()) do
729 AbstractSection.parse_dynamic(self, k)
730 if luci.http.formvalue("cbi.submit") then
733 AbstractSection.parse_optionals(self, k)
737 -- Verifies scope of sections
738 function TypedSection.checkscope(self, section)
739 -- Check if we are not excluded
740 if self.filter and not self:filter(section) then
744 -- Check if at least one dependency is met
745 if #self.deps > 0 and self:cfgvalue(section) then
748 for k, v in ipairs(self.deps) do
749 if self:cfgvalue(section)[v.option] == v.value then
759 return self:validate(section)
763 -- Dummy validate function
764 function TypedSection.validate(self, section)
770 AbstractValue - An abstract Value Type
771 null: Value can be empty
772 valid: A function returning the value if it is valid otherwise nil
773 depends: A table of option => value pairs of which one must be true
774 default: The default value
775 size: The size of the input fields
776 rmempty: Unset value if empty
777 optional: This value is optional (see AbstractSection.optionals)
779 AbstractValue = class(Node)
781 function AbstractValue.__init__(self, map, section, option, ...)
782 Node.__init__(self, ...)
783 self.section = section
786 self.config = map.config
787 self.tag_invalid = {}
788 self.tag_missing = {}
793 self.track_missing = false
797 self.optional = false
799 -- Use defaults from UVL
800 if not self.override_scheme
801 and self.map:get_scheme(self.section.sectiontype, self.option) then
802 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
803 self.rmempty = not vs.required
804 self.cast = (vs.type == "list") and "list" or "string"
805 self.title = self.title or vs.title
806 self.description = self.description or vs.descr
808 if vs.depends and not self.override_dependencies then
809 for i, deps in ipairs(vs.depends) do
810 deps = _uvl_strip_remote_dependencies(deps)
819 -- Add a dependencie to another section field
820 function AbstractValue.depends(self, field, value)
822 if type(field) == "string" then
829 table.insert(self.deps, {deps=deps, add=""})
832 -- Generates the unique CBID
833 function AbstractValue.cbid(self, section)
834 return "cbid."..self.map.config.."."..section.."."..self.option
837 -- Return whether this object should be created
838 function AbstractValue.formcreated(self, section)
839 local key = "cbi.opt."..self.config.."."..section
840 return (luci.http.formvalue(key) == self.option)
843 -- Returns the formvalue for this object
844 function AbstractValue.formvalue(self, section)
845 return luci.http.formvalue(self:cbid(section))
848 function AbstractValue.additional(self, value)
849 self.optional = value
852 function AbstractValue.mandatory(self, value)
853 self.rmempty = not value
856 function AbstractValue.parse(self, section)
857 local fvalue = self:formvalue(section)
858 local cvalue = self:cfgvalue(section)
860 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
861 fvalue = self:transform(self:validate(fvalue, section))
863 self.tag_invalid[section] = true
865 if fvalue and not (fvalue == cvalue) then
866 self:write(section, fvalue)
868 else -- Unset the UCI or error
869 if self.rmempty or self.optional then
871 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
872 self.tag_missing[section] = true
877 -- Render if this value exists or if it is mandatory
878 function AbstractValue.render(self, s, scope)
879 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
882 scope.cbid = self:cbid(s)
883 scope.striptags = luci.util.striptags
885 scope.ifattr = function(cond,key,val)
887 return string.format(
888 ' %s="%s"', tostring(key),
889 luci.util.pcdata(tostring( val
891 or (type(self[key]) ~= "function" and self[key])
899 scope.attr = function(...)
900 return scope.ifattr( true, ... )
903 Node.render(self, scope)
907 -- Return the UCI value of this object
908 function AbstractValue.cfgvalue(self, section)
909 local value = self.map:get(section, self.option)
910 if not self.cast or self.cast == type(value) then
912 elseif self.cast == "string" then
913 if type(value) == "table" then
916 elseif self.cast == "table" then
921 -- Validate the form value
922 function AbstractValue.validate(self, value)
926 AbstractValue.transform = AbstractValue.validate
930 function AbstractValue.write(self, section, value)
931 return self.map:set(section, self.option, value)
935 function AbstractValue.remove(self, section)
936 return self.map:del(section, self.option)
943 Value - A one-line value
944 maxlength: The maximum length
946 Value = class(AbstractValue)
948 function Value.__init__(self, ...)
949 AbstractValue.__init__(self, ...)
950 self.template = "cbi/value"
955 function Value.value(self, key, val)
957 table.insert(self.keylist, tostring(key))
958 table.insert(self.vallist, tostring(val))
962 -- DummyValue - This does nothing except being there
963 DummyValue = class(AbstractValue)
965 function DummyValue.__init__(self, ...)
966 AbstractValue.__init__(self, ...)
967 self.template = "cbi/dvalue"
971 function DummyValue.parse(self)
977 Flag - A flag being enabled or disabled
979 Flag = class(AbstractValue)
981 function Flag.__init__(self, ...)
982 AbstractValue.__init__(self, ...)
983 self.template = "cbi/fvalue"
989 -- A flag can only have two states: set or unset
990 function Flag.parse(self, section)
991 local fvalue = self:formvalue(section)
994 fvalue = self.enabled
996 fvalue = self.disabled
999 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1000 if not(fvalue == self:cfgvalue(section)) then
1001 self:write(section, fvalue)
1004 self:remove(section)
1011 ListValue - A one-line value predefined in a list
1012 widget: The widget that will be used (select, radio)
1014 ListValue = class(AbstractValue)
1016 function ListValue.__init__(self, ...)
1017 AbstractValue.__init__(self, ...)
1018 self.template = "cbi/lvalue"
1023 self.widget = "select"
1025 if not self.override_scheme
1026 and self.map:get_scheme(self.section.sectiontype, self.option) then
1027 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1028 if self.value and vs.values and not self.override_values then
1029 if self.rmempty or self.optional then
1032 for k, v in pairs(vs.values) do
1034 if vs.enum_depends and vs.enum_depends[k] then
1035 deps = _uvl_strip_remote_dependencies(vs.enum_depends[k])
1037 self:value(k, v, unpack(deps))
1043 function ListValue.value(self, key, val, ...)
1045 table.insert(self.keylist, tostring(key))
1046 table.insert(self.vallist, tostring(val))
1048 for i, deps in ipairs({...}) do
1049 table.insert(self.deps, {add = "-"..key, deps=deps})
1053 function ListValue.validate(self, val)
1054 if luci.util.contains(self.keylist, val) then
1064 MultiValue - Multiple delimited values
1065 widget: The widget that will be used (select, checkbox)
1066 delimiter: The delimiter that will separate the values (default: " ")
1068 MultiValue = class(AbstractValue)
1070 function MultiValue.__init__(self, ...)
1071 AbstractValue.__init__(self, ...)
1072 self.template = "cbi/mvalue"
1077 self.widget = "checkbox"
1078 self.delimiter = " "
1081 function MultiValue.render(self, ...)
1082 if self.widget == "select" and not self.size then
1083 self.size = #self.vallist
1086 AbstractValue.render(self, ...)
1089 function MultiValue.value(self, key, val)
1091 table.insert(self.keylist, tostring(key))
1092 table.insert(self.vallist, tostring(val))
1095 function MultiValue.valuelist(self, section)
1096 local val = self:cfgvalue(section)
1098 if not(type(val) == "string") then
1102 return luci.util.split(val, self.delimiter)
1105 function MultiValue.validate(self, val)
1106 val = (type(val) == "table") and val or {val}
1110 for i, value in ipairs(val) do
1111 if luci.util.contains(self.keylist, value) then
1112 result = result and (result .. self.delimiter .. value) or value
1120 StaticList = class(MultiValue)
1122 function StaticList.__init__(self, ...)
1123 MultiValue.__init__(self, ...)
1125 self.valuelist = self.cfgvalue
1127 if not self.override_scheme
1128 and self.map:get_scheme(self.section.sectiontype, self.option) then
1129 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1130 if self.value and vs.values and not self.override_values then
1131 for k, v in pairs(vs.values) do
1138 function StaticList.validate(self, value)
1139 value = (type(value) == "table") and value or {value}
1142 for i, v in ipairs(value) do
1143 if luci.util.contains(self.valuelist, v) then
1144 table.insert(valid, v)
1151 DynamicList = class(AbstractValue)
1153 function DynamicList.__init__(self, ...)
1154 AbstractValue.__init__(self, ...)
1155 self.template = "cbi/dynlist"
1161 function DynamicList.value(self, key, val)
1163 table.insert(self.keylist, tostring(key))
1164 table.insert(self.vallist, tostring(val))
1167 function DynamicList.validate(self, value, section)
1168 value = (type(value) == "table") and value or {value}
1171 for i, v in ipairs(value) do
1173 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1174 table.insert(valid, v)
1183 TextValue - A multi-line value
1186 TextValue = class(AbstractValue)
1188 function TextValue.__init__(self, ...)
1189 AbstractValue.__init__(self, ...)
1190 self.template = "cbi/tvalue"
1196 Button = class(AbstractValue)
1198 function Button.__init__(self, ...)
1199 AbstractValue.__init__(self, ...)
1200 self.template = "cbi/button"
1201 self.inputstyle = nil