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")
34 local uci = require("luci.model.uci")
35 local class = luci.util.class
36 local instanceof = luci.util.instanceof
44 CREATE_PREFIX = "cbi.cts."
45 REMOVE_PREFIX = "cbi.rts."
47 -- Loads a CBI map from given file, creating an environment and returns it
48 function load(cbimap, ...)
51 require("luci.config")
54 local cbidir = luci.util.libpath() .. "/model/cbi/"
55 local func, err = loadfile(cbidir..cbimap..".lua")
61 luci.i18n.loadc("cbi")
63 luci.util.resfenv(func)
64 luci.util.updfenv(func, luci.cbi)
65 luci.util.extfenv(func, "translate", luci.i18n.translate)
66 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
67 luci.util.extfenv(func, "arg", {...})
71 for i, map in ipairs(maps) do
72 if not instanceof(map, Node) then
73 error("CBI map returns no valid map object!")
81 local function _uvl_validate_section(node, name)
82 local co = node.map:get()
84 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
85 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
87 local stat, err = node.map.validator:validate_section(node.config, name, co)
90 if err.code == luci.uvl.errors.ERR_DEPENDENCY then
91 node.tag_deperror[name] = true
93 node.tag_invalid[name] = true
95 for i, v in ipairs(err.childs) do
96 if v.option and node.fields[v.option] then
97 if v.code == luci.uvl.errors.ERR_OPTION then
98 local subcode = v.childs and v.childs[1] and v.childs[1].code
99 if subcode == luci.uvl.errors.ERR_DEPENDENCY then
100 node.fields[v.option].tag_reqerror[name] = true
101 elseif subcode == luci.uvl.errors.ERR_OPT_REQUIRED then
102 node.fields[v.option].tag_missing[name] = true
103 node.tag_deperror[name] = true
105 node.fields[v.option].tag_invalid[name] = true
114 local function _uvl_strip_remote_dependencies(deps)
117 for k, v in pairs(deps) do
118 k = k:gsub("%$config%.%$section%.", "")
119 if k:match("^[%w_]+$") and type(v) == "string" then
128 -- Node pseudo abstract class
131 function Node.__init__(self, title, description)
133 self.title = title or ""
134 self.description = description or ""
135 self.template = "cbi/node"
139 function Node._i18n(self, config, section, option, title, description)
142 if type(luci.i18n) == "table" then
144 local key = config and config:gsub("[^%w]+", "") or ""
146 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
147 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
149 self.title = title or luci.i18n.translate( key, option or section or config )
150 self.description = description or luci.i18n.translate( key .. "_desc", "" )
154 -- Append child nodes
155 function Node.append(self, obj)
156 table.insert(self.children, obj)
159 -- Parse this node and its children
160 function Node.parse(self, ...)
161 for k, child in ipairs(self.children) do
167 function Node.render(self, scope)
171 luci.template.render(self.template, scope)
174 -- Render the children
175 function Node.render_children(self, ...)
176 for k, node in ipairs(self.children) do
183 A simple template element
185 Template = class(Node)
187 function Template.__init__(self, template)
189 self.template = template
192 function Template.render(self)
193 luci.template.render(self.template, {self=self})
198 Map - A map describing a configuration file
202 function Map.__init__(self, config, ...)
203 Node.__init__(self, ...)
204 Node._i18n(self, config, nil, nil, ...)
207 self.parsechain = {self.config}
208 self.template = "cbi/map"
209 self.uci = uci.cursor()
211 if not self.uci:load(self.config) then
212 error("Unable to read UCI data: " .. self.config)
215 self.validator = luci.uvl.UVL()
216 self.scheme = self.validator:get_scheme(self.config)
220 function Map.get_scheme(self, sectiontype, option)
222 return self.scheme and self.scheme.sections[sectiontype]
224 return self.scheme and self.scheme.variables[sectiontype]
225 and self.scheme.variables[sectiontype][option]
230 -- Chain foreign config
231 function Map.chain(self, config)
232 table.insert(self.parsechain, config)
235 -- Use optimized UCI writing
236 function Map.parse(self, ...)
237 Node.parse(self, ...)
240 for i, config in ipairs(self.parsechain) do
241 self.uci:save(config)
243 if luci.http.formvalue("cbi.apply") then
244 for i, config in ipairs(self.parsechain) do
245 self.uci:commit(config)
246 self.uci:apply(config)
248 -- Refresh data because commit changes section names
249 self.uci:load(config)
253 Node.parse(self, ...)
256 for i, config in ipairs(self.parsechain) do
257 self.uci:unload(config)
262 -- Creates a child section
263 function Map.section(self, class, ...)
264 if instanceof(class, AbstractSection) then
265 local obj = class(self, ...)
269 error("class must be a descendent of AbstractSection")
274 function Map.add(self, sectiontype)
275 return self.uci:add(self.config, sectiontype)
279 function Map.set(self, section, option, value)
281 return self.uci:set(self.config, section, option, value)
283 return self.uci:set(self.config, section, value)
288 function Map.del(self, section, option)
290 return self.uci:delete(self.config, section, option)
292 return self.uci:delete(self.config, section)
297 function Map.get(self, section, option)
299 return self.uci:get_all(self.config)
301 return self.uci:get(self.config, section, option)
303 return self.uci:get_all(self.config, section)
313 Page.__init__ = Node.__init__
314 Page.parse = function() end
318 SimpleForm - A Simple non-UCI form
320 SimpleForm = class(Node)
322 function SimpleForm.__init__(self, config, title, description, data)
323 Node.__init__(self, title, description)
325 self.data = data or {}
326 self.template = "cbi/simpleform"
330 function SimpleForm.parse(self, ...)
331 if luci.http.formvalue("cbi.submit") then
332 Node.parse(self, 1, ...)
336 for k, j in ipairs(self.children) do
337 for i, v in ipairs(j.children) do
339 and (not v.tag_missing or not v.tag_missing[1])
340 and (not v.tag_invalid or not v.tag_invalid[1])
345 not luci.http.formvalue("cbi.submit") and 0
349 self.dorender = not self.handle or self:handle(state, self.data) ~= false
352 function SimpleForm.render(self, ...)
353 if self.dorender then
354 Node.render(self, ...)
358 function SimpleForm.section(self, class, ...)
359 if instanceof(class, AbstractSection) then
360 local obj = class(self, ...)
364 error("class must be a descendent of AbstractSection")
368 -- Creates a child field
369 function SimpleForm.field(self, class, ...)
371 for k, v in ipairs(self.children) do
372 if instanceof(v, SimpleSection) then
378 section = self:section(SimpleSection)
381 if instanceof(class, AbstractValue) then
382 local obj = class(self, section, ...)
383 obj.track_missing = true
387 error("class must be a descendent of AbstractValue")
391 function SimpleForm.set(self, section, option, value)
392 self.data[option] = value
396 function SimpleForm.del(self, section, option)
397 self.data[option] = nil
401 function SimpleForm.get(self, section, option)
402 return self.data[option]
406 function SimpleForm.get_scheme()
415 AbstractSection = class(Node)
417 function AbstractSection.__init__(self, map, sectiontype, ...)
418 Node.__init__(self, ...)
419 self.sectiontype = sectiontype
421 self.config = map.config
426 self.tag_invalid = {}
427 self.tag_deperror = {}
430 self.addremove = false
434 -- Appends a new option
435 function AbstractSection.option(self, class, option, ...)
436 -- Autodetect from UVL
437 if class == true and self.map:get_scheme(self.sectiontype, option) then
438 local vs = self.map:get_scheme(self.sectiontype, option)
439 if vs.type == "boolean" then
441 elseif vs.type == "list" then
443 elseif vs.type == "enum" or vs.type == "reference" then
450 if instanceof(class, AbstractValue) then
451 local obj = class(self.map, self, option, ...)
453 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
456 self.fields[option] = obj
458 elseif class == true then
459 error("No valid class was given and autodetection failed.")
461 error("class must be a descendant of AbstractValue")
465 -- Parse optional options
466 function AbstractSection.parse_optionals(self, section)
467 if not self.optional then
471 self.optionals[section] = {}
473 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
474 for k,v in ipairs(self.children) do
475 if v.optional and not v:cfgvalue(section) then
476 if field == v.option then
479 table.insert(self.optionals[section], v)
484 if field and #field > 0 and self.dynamic then
485 self:add_dynamic(field)
489 -- Add a dynamic option
490 function AbstractSection.add_dynamic(self, field, optional)
491 local o = self:option(Value, field, field)
492 o.optional = optional
495 -- Parse all dynamic options
496 function AbstractSection.parse_dynamic(self, section)
497 if not self.dynamic then
501 local arr = luci.util.clone(self:cfgvalue(section))
502 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
503 for k, v in pairs(form) do
507 for key,val in pairs(arr) do
510 for i,c in ipairs(self.children) do
511 if c.option == key then
516 if create and key:sub(1, 1) ~= "." then
517 self:add_dynamic(key, true)
522 -- Returns the section's UCI table
523 function AbstractSection.cfgvalue(self, section)
524 return self.map:get(section)
527 -- Removes the section
528 function AbstractSection.remove(self, section)
529 return self.map:del(section)
532 -- Creates the section
533 function AbstractSection.create(self, section)
537 stat = self.map:set(section, nil, self.sectiontype)
539 section = self.map:add(self.sectiontype)
544 for k,v in pairs(self.children) do
546 self.map:set(section, v.option, v.default)
550 for k,v in pairs(self.defaults) do
551 self.map:set(section, k, v)
559 SimpleSection = class(AbstractSection)
561 function SimpleSection.__init__(self, form, ...)
562 AbstractSection.__init__(self, form, nil, ...)
563 self.template = "cbi/nullsection"
567 Table = class(AbstractSection)
569 function Table.__init__(self, form, data, ...)
570 local datasource = {}
571 datasource.config = "table"
574 function datasource.get(self, section, option)
575 return data[section] and data[section][option]
578 function datasource.del(...)
582 function datasource.get_scheme()
586 AbstractSection.__init__(self, datasource, "table", ...)
587 self.template = "cbi/tblsection"
588 self.rowcolors = true
589 self.anonymous = true
592 function Table.parse(self)
593 for i, k in ipairs(self:cfgsections()) do
594 if luci.http.formvalue("cbi.submit") then
600 function Table.cfgsections(self)
603 for i, v in luci.util.kspairs(self.data) do
604 table.insert(sections, i)
613 NamedSection - A fixed configuration section defined by its name
615 NamedSection = class(AbstractSection)
617 function NamedSection.__init__(self, map, section, stype, ...)
618 AbstractSection.__init__(self, map, stype, ...)
619 Node._i18n(self, map.config, section, nil, ...)
622 self.addremove = false
624 -- Use defaults from UVL
625 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
626 local vs = self.map:get_scheme(self.sectiontype)
627 self.addremove = not vs.unique and not vs.required
628 self.dynamic = vs.dynamic
629 self.title = self.title or vs.title
630 self.description = self.description or vs.descr
633 self.template = "cbi/nsection"
634 self.section = section
637 function NamedSection.parse(self)
638 local s = self.section
639 local active = self:cfgvalue(s)
641 if self.addremove then
642 local path = self.config.."."..s
643 if active then -- Remove the section
644 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
647 else -- Create and apply default values
648 if luci.http.formvalue("cbi.cns."..path) then
656 AbstractSection.parse_dynamic(self, s)
657 if luci.http.formvalue("cbi.submit") then
660 if not self.override_scheme and self.map.scheme then
661 _uvl_validate_section(self, s)
664 AbstractSection.parse_optionals(self, s)
670 TypedSection - A (set of) configuration section(s) defined by the type
671 addremove: Defines whether the user can add/remove sections of this type
672 anonymous: Allow creating anonymous sections
673 validate: a validation function returning nil if the section is invalid
675 TypedSection = class(AbstractSection)
677 function TypedSection.__init__(self, map, type, ...)
678 AbstractSection.__init__(self, map, type, ...)
679 Node._i18n(self, map.config, type, nil, ...)
681 self.template = "cbi/tsection"
683 self.anonymous = false
685 -- Use defaults from UVL
686 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
687 local vs = self.map:get_scheme(self.sectiontype)
688 self.addremove = not vs.unique and not vs.required
689 self.dynamic = vs.dynamic
690 self.anonymous = not vs.named
691 self.title = self.title or vs.title
692 self.description = self.description or vs.descr
696 -- Return all matching UCI sections for this TypedSection
697 function TypedSection.cfgsections(self)
699 self.map.uci:foreach(self.map.config, self.sectiontype,
701 if self:checkscope(section[".name"]) then
702 table.insert(sections, section[".name"])
709 -- Limits scope to sections that have certain option => value pairs
710 function TypedSection.depends(self, option, value)
711 table.insert(self.deps, {option=option, value=value})
714 function TypedSection.parse(self)
715 if self.addremove then
717 local crval = REMOVE_PREFIX .. self.config
718 local name = luci.http.formvaluetable(crval)
719 for k,v in pairs(name) do
720 if self:cfgvalue(k) and self:checkscope(k) then
727 for i, k in ipairs(self:cfgsections()) do
728 AbstractSection.parse_dynamic(self, k)
729 if luci.http.formvalue("cbi.submit") then
732 if not self.override_scheme and self.map.scheme then
733 _uvl_validate_section(self, k)
736 AbstractSection.parse_optionals(self, k)
739 if self.addremove then
741 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
742 local name = luci.http.formvalue(crval)
743 if self.anonymous then
749 -- Ignore if it already exists
750 if self:cfgvalue(name) then
754 name = self:checkscope(name)
757 self.err_invalid = true
760 if name and #name > 0 then
768 -- Verifies scope of sections
769 function TypedSection.checkscope(self, section)
770 -- Check if we are not excluded
771 if self.filter and not self:filter(section) then
775 -- Check if at least one dependency is met
776 if #self.deps > 0 and self:cfgvalue(section) then
779 for k, v in ipairs(self.deps) do
780 if self:cfgvalue(section)[v.option] == v.value then
790 return self:validate(section)
794 -- Dummy validate function
795 function TypedSection.validate(self, section)
801 AbstractValue - An abstract Value Type
802 null: Value can be empty
803 valid: A function returning the value if it is valid otherwise nil
804 depends: A table of option => value pairs of which one must be true
805 default: The default value
806 size: The size of the input fields
807 rmempty: Unset value if empty
808 optional: This value is optional (see AbstractSection.optionals)
810 AbstractValue = class(Node)
812 function AbstractValue.__init__(self, map, section, option, ...)
813 Node.__init__(self, ...)
814 self.section = section
817 self.config = map.config
818 self.tag_invalid = {}
819 self.tag_missing = {}
820 self.tag_reqerror = {}
825 self.track_missing = false
829 self.optional = false
831 -- Use defaults from UVL
832 if not self.override_scheme
833 and self.map:get_scheme(self.section.sectiontype, self.option) then
834 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
835 self.rmempty = not vs.required
836 self.cast = (vs.type == "list") and "list" or "string"
837 self.title = self.title or vs.title
838 self.description = self.description or vs.descr
840 if vs.depends and not self.override_dependencies then
841 for i, deps in ipairs(vs.depends) do
842 deps = _uvl_strip_remote_dependencies(deps)
851 -- Add a dependencie to another section field
852 function AbstractValue.depends(self, field, value)
854 if type(field) == "string" then
861 table.insert(self.deps, {deps=deps, add=""})
864 -- Generates the unique CBID
865 function AbstractValue.cbid(self, section)
866 return "cbid."..self.map.config.."."..section.."."..self.option
869 -- Return whether this object should be created
870 function AbstractValue.formcreated(self, section)
871 local key = "cbi.opt."..self.config.."."..section
872 return (luci.http.formvalue(key) == self.option)
875 -- Returns the formvalue for this object
876 function AbstractValue.formvalue(self, section)
877 return luci.http.formvalue(self:cbid(section))
880 function AbstractValue.additional(self, value)
881 self.optional = value
884 function AbstractValue.mandatory(self, value)
885 self.rmempty = not value
888 function AbstractValue.parse(self, section)
889 local fvalue = self:formvalue(section)
890 local cvalue = self:cfgvalue(section)
892 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
893 fvalue = self:transform(self:validate(fvalue, section))
895 self.tag_invalid[section] = true
897 if fvalue and not (fvalue == cvalue) then
898 self:write(section, fvalue)
900 else -- Unset the UCI or error
901 if self.rmempty or self.optional then
903 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
904 self.tag_missing[section] = true
909 -- Render if this value exists or if it is mandatory
910 function AbstractValue.render(self, s, scope)
911 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
914 scope.cbid = self:cbid(s)
915 scope.striptags = luci.util.striptags
917 scope.ifattr = function(cond,key,val)
919 return string.format(
920 ' %s="%s"', tostring(key),
921 luci.util.pcdata(tostring( val
923 or (type(self[key]) ~= "function" and self[key])
931 scope.attr = function(...)
932 return scope.ifattr( true, ... )
935 Node.render(self, scope)
939 -- Return the UCI value of this object
940 function AbstractValue.cfgvalue(self, section)
941 local value = self.map:get(section, self.option)
942 if not self.cast or self.cast == type(value) then
944 elseif self.cast == "string" then
945 if type(value) == "table" then
948 elseif self.cast == "table" then
953 -- Validate the form value
954 function AbstractValue.validate(self, value)
958 AbstractValue.transform = AbstractValue.validate
962 function AbstractValue.write(self, section, value)
963 return self.map:set(section, self.option, value)
967 function AbstractValue.remove(self, section)
968 return self.map:del(section, self.option)
975 Value - A one-line value
976 maxlength: The maximum length
978 Value = class(AbstractValue)
980 function Value.__init__(self, ...)
981 AbstractValue.__init__(self, ...)
982 self.template = "cbi/value"
987 function Value.value(self, key, val)
989 table.insert(self.keylist, tostring(key))
990 table.insert(self.vallist, tostring(val))
994 -- DummyValue - This does nothing except being there
995 DummyValue = class(AbstractValue)
997 function DummyValue.__init__(self, ...)
998 AbstractValue.__init__(self, ...)
999 self.template = "cbi/dvalue"
1003 function DummyValue.parse(self)
1009 Flag - A flag being enabled or disabled
1011 Flag = class(AbstractValue)
1013 function Flag.__init__(self, ...)
1014 AbstractValue.__init__(self, ...)
1015 self.template = "cbi/fvalue"
1021 -- A flag can only have two states: set or unset
1022 function Flag.parse(self, section)
1023 local fvalue = self:formvalue(section)
1026 fvalue = self.enabled
1028 fvalue = self.disabled
1031 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1032 if not(fvalue == self:cfgvalue(section)) then
1033 self:write(section, fvalue)
1036 self:remove(section)
1043 ListValue - A one-line value predefined in a list
1044 widget: The widget that will be used (select, radio)
1046 ListValue = class(AbstractValue)
1048 function ListValue.__init__(self, ...)
1049 AbstractValue.__init__(self, ...)
1050 self.template = "cbi/lvalue"
1055 self.widget = "select"
1057 if not self.override_scheme
1058 and self.map:get_scheme(self.section.sectiontype, self.option) then
1059 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1060 if self.value and vs.values and not self.override_values then
1061 if self.rmempty or self.optional then
1064 for k, v in pairs(vs.values) do
1066 if not self.override_dependencies
1067 and vs.enum_depends and vs.enum_depends[k] then
1068 for i, dep in ipairs(vs.enum_depends[k]) do
1069 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1072 self:value(k, v, unpack(deps))
1078 function ListValue.value(self, key, val, ...)
1079 if luci.util.contains(self.keylist, key) then
1084 table.insert(self.keylist, tostring(key))
1085 table.insert(self.vallist, tostring(val))
1087 for i, deps in ipairs({...}) do
1088 table.insert(self.deps, {add = "-"..key, deps=deps})
1092 function ListValue.validate(self, val)
1093 if luci.util.contains(self.keylist, val) then
1103 MultiValue - Multiple delimited values
1104 widget: The widget that will be used (select, checkbox)
1105 delimiter: The delimiter that will separate the values (default: " ")
1107 MultiValue = class(AbstractValue)
1109 function MultiValue.__init__(self, ...)
1110 AbstractValue.__init__(self, ...)
1111 self.template = "cbi/mvalue"
1116 self.widget = "checkbox"
1117 self.delimiter = " "
1120 function MultiValue.render(self, ...)
1121 if self.widget == "select" and not self.size then
1122 self.size = #self.vallist
1125 AbstractValue.render(self, ...)
1128 function MultiValue.value(self, key, val)
1129 if luci.util.contains(self.keylist, key) then
1134 table.insert(self.keylist, tostring(key))
1135 table.insert(self.vallist, tostring(val))
1138 function MultiValue.valuelist(self, section)
1139 local val = self:cfgvalue(section)
1141 if not(type(val) == "string") then
1145 return luci.util.split(val, self.delimiter)
1148 function MultiValue.validate(self, val)
1149 val = (type(val) == "table") and val or {val}
1153 for i, value in ipairs(val) do
1154 if luci.util.contains(self.keylist, value) then
1155 result = result and (result .. self.delimiter .. value) or value
1163 StaticList = class(MultiValue)
1165 function StaticList.__init__(self, ...)
1166 MultiValue.__init__(self, ...)
1168 self.valuelist = self.cfgvalue
1170 if not self.override_scheme
1171 and self.map:get_scheme(self.section.sectiontype, self.option) then
1172 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1173 if self.value and vs.values and not self.override_values then
1174 for k, v in pairs(vs.values) do
1181 function StaticList.validate(self, value)
1182 value = (type(value) == "table") and value or {value}
1185 for i, v in ipairs(value) do
1186 if luci.util.contains(self.valuelist, v) then
1187 table.insert(valid, v)
1194 DynamicList = class(AbstractValue)
1196 function DynamicList.__init__(self, ...)
1197 AbstractValue.__init__(self, ...)
1198 self.template = "cbi/dynlist"
1204 function DynamicList.value(self, key, val)
1206 table.insert(self.keylist, tostring(key))
1207 table.insert(self.vallist, tostring(val))
1210 function DynamicList.validate(self, value, section)
1211 value = (type(value) == "table") and value or {value}
1214 for i, v in ipairs(value) do
1216 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1217 table.insert(valid, v)
1226 TextValue - A multi-line value
1229 TextValue = class(AbstractValue)
1231 function TextValue.__init__(self, ...)
1232 AbstractValue.__init__(self, ...)
1233 self.template = "cbi/tvalue"
1239 Button = class(AbstractValue)
1241 function Button.__init__(self, ...)
1242 AbstractValue.__init__(self, ...)
1243 self.template = "cbi/button"
1244 self.inputstyle = nil