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:is(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:is(luci.uvl.errors.ERR_DEPENDENCY) then
98 node.fields[v.option].tag_reqerror[name] = true
99 elseif v:is(luci.uvl.errors.ERR_OPT_REQUIRED) then
100 node.fields[v.option].tag_missing[name] = true
101 node.tag_deperror[name] = true
102 elseif v:is(luci.uvl.errors.ERR_OPTION) then
103 node.fields[v.option].tag_invalid[name] = true
111 local function _uvl_strip_remote_dependencies(deps)
114 for k, v in pairs(deps) do
115 k = k:gsub("%$config%.%$section%.", "")
116 if k:match("^[%w_]+$") and type(v) == "string" then
125 -- Node pseudo abstract class
128 function Node.__init__(self, title, description)
130 self.title = title or ""
131 self.description = description or ""
132 self.template = "cbi/node"
136 function Node._i18n(self, config, section, option, title, description)
139 if type(luci.i18n) == "table" then
141 local key = config and config:gsub("[^%w]+", "") or ""
143 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
144 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
146 self.title = title or luci.i18n.translate( key, option or section or config )
147 self.description = description or luci.i18n.translate( key .. "_desc", "" )
151 -- Append child nodes
152 function Node.append(self, obj)
153 table.insert(self.children, obj)
156 -- Parse this node and its children
157 function Node.parse(self, ...)
158 for k, child in ipairs(self.children) do
164 function Node.render(self, scope)
168 luci.template.render(self.template, scope)
171 -- Render the children
172 function Node.render_children(self, ...)
173 for k, node in ipairs(self.children) do
180 A simple template element
182 Template = class(Node)
184 function Template.__init__(self, template)
186 self.template = template
189 function Template.render(self)
190 luci.template.render(self.template, {self=self})
195 Map - A map describing a configuration file
199 function Map.__init__(self, config, ...)
200 Node.__init__(self, ...)
201 Node._i18n(self, config, nil, nil, ...)
204 self.parsechain = {self.config}
205 self.template = "cbi/map"
206 self.uci = uci.cursor()
208 if not self.uci:load(self.config) then
209 error("Unable to read UCI data: " .. self.config)
212 self.validator = luci.uvl.UVL()
213 self.scheme = self.validator:get_scheme(self.config)
217 function Map.get_scheme(self, sectiontype, option)
219 return self.scheme and self.scheme.sections[sectiontype]
221 return self.scheme and self.scheme.variables[sectiontype]
222 and self.scheme.variables[sectiontype][option]
227 -- Chain foreign config
228 function Map.chain(self, config)
229 table.insert(self.parsechain, config)
232 -- Use optimized UCI writing
233 function Map.parse(self, ...)
234 Node.parse(self, ...)
237 for i, config in ipairs(self.parsechain) do
238 self.uci:save(config)
240 if luci.http.formvalue("cbi.apply") then
241 for i, config in ipairs(self.parsechain) do
242 self.uci:commit(config)
243 self.uci:apply(config)
245 -- Refresh data because commit changes section names
246 self.uci:load(config)
250 Node.parse(self, ...)
253 for i, config in ipairs(self.parsechain) do
254 self.uci:unload(config)
259 -- Creates a child section
260 function Map.section(self, class, ...)
261 if instanceof(class, AbstractSection) then
262 local obj = class(self, ...)
266 error("class must be a descendent of AbstractSection")
271 function Map.add(self, sectiontype)
272 return self.uci:add(self.config, sectiontype)
276 function Map.set(self, section, option, value)
278 return self.uci:set(self.config, section, option, value)
280 return self.uci:set(self.config, section, value)
285 function Map.del(self, section, option)
287 return self.uci:delete(self.config, section, option)
289 return self.uci:delete(self.config, section)
294 function Map.get(self, section, option)
296 return self.uci:get_all(self.config)
298 return self.uci:get(self.config, section, option)
300 return self.uci:get_all(self.config, section)
310 Page.__init__ = Node.__init__
311 Page.parse = function() end
315 SimpleForm - A Simple non-UCI form
317 SimpleForm = class(Node)
319 function SimpleForm.__init__(self, config, title, description, data)
320 Node.__init__(self, title, description)
322 self.data = data or {}
323 self.template = "cbi/simpleform"
327 function SimpleForm.parse(self, ...)
328 if luci.http.formvalue("cbi.submit") then
329 Node.parse(self, 1, ...)
333 for k, j in ipairs(self.children) do
334 for i, v in ipairs(j.children) do
336 and (not v.tag_missing or not v.tag_missing[1])
337 and (not v.tag_invalid or not v.tag_invalid[1])
342 not luci.http.formvalue("cbi.submit") and 0
346 self.dorender = not self.handle or self:handle(state, self.data) ~= false
349 function SimpleForm.render(self, ...)
350 if self.dorender then
351 Node.render(self, ...)
355 function SimpleForm.section(self, class, ...)
356 if instanceof(class, AbstractSection) then
357 local obj = class(self, ...)
361 error("class must be a descendent of AbstractSection")
365 -- Creates a child field
366 function SimpleForm.field(self, class, ...)
368 for k, v in ipairs(self.children) do
369 if instanceof(v, SimpleSection) then
375 section = self:section(SimpleSection)
378 if instanceof(class, AbstractValue) then
379 local obj = class(self, section, ...)
380 obj.track_missing = true
384 error("class must be a descendent of AbstractValue")
388 function SimpleForm.set(self, section, option, value)
389 self.data[option] = value
393 function SimpleForm.del(self, section, option)
394 self.data[option] = nil
398 function SimpleForm.get(self, section, option)
399 return self.data[option]
403 function SimpleForm.get_scheme()
412 AbstractSection = class(Node)
414 function AbstractSection.__init__(self, map, sectiontype, ...)
415 Node.__init__(self, ...)
416 self.sectiontype = sectiontype
418 self.config = map.config
423 self.tag_invalid = {}
424 self.tag_deperror = {}
427 self.addremove = false
431 -- Appends a new option
432 function AbstractSection.option(self, class, option, ...)
433 -- Autodetect from UVL
434 if class == true and self.map:get_scheme(self.sectiontype, option) then
435 local vs = self.map:get_scheme(self.sectiontype, option)
436 if vs.type == "boolean" then
438 elseif vs.type == "list" then
440 elseif vs.type == "enum" or vs.type == "reference" then
447 if instanceof(class, AbstractValue) then
448 local obj = class(self.map, self, option, ...)
450 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
453 self.fields[option] = obj
455 elseif class == true then
456 error("No valid class was given and autodetection failed.")
458 error("class must be a descendant of AbstractValue")
462 -- Parse optional options
463 function AbstractSection.parse_optionals(self, section)
464 if not self.optional then
468 self.optionals[section] = {}
470 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
471 for k,v in ipairs(self.children) do
472 if v.optional and not v:cfgvalue(section) then
473 if field == v.option then
476 table.insert(self.optionals[section], v)
481 if field and #field > 0 and self.dynamic then
482 self:add_dynamic(field)
486 -- Add a dynamic option
487 function AbstractSection.add_dynamic(self, field, optional)
488 local o = self:option(Value, field, field)
489 o.optional = optional
492 -- Parse all dynamic options
493 function AbstractSection.parse_dynamic(self, section)
494 if not self.dynamic then
498 local arr = luci.util.clone(self:cfgvalue(section))
499 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
500 for k, v in pairs(form) do
504 for key,val in pairs(arr) do
507 for i,c in ipairs(self.children) do
508 if c.option == key then
513 if create and key:sub(1, 1) ~= "." then
514 self:add_dynamic(key, true)
519 -- Returns the section's UCI table
520 function AbstractSection.cfgvalue(self, section)
521 return self.map:get(section)
524 -- Removes the section
525 function AbstractSection.remove(self, section)
526 return self.map:del(section)
529 -- Creates the section
530 function AbstractSection.create(self, section)
534 stat = self.map:set(section, nil, self.sectiontype)
536 section = self.map:add(self.sectiontype)
541 for k,v in pairs(self.children) do
543 self.map:set(section, v.option, v.default)
547 for k,v in pairs(self.defaults) do
548 self.map:set(section, k, v)
556 SimpleSection = class(AbstractSection)
558 function SimpleSection.__init__(self, form, ...)
559 AbstractSection.__init__(self, form, nil, ...)
560 self.template = "cbi/nullsection"
564 Table = class(AbstractSection)
566 function Table.__init__(self, form, data, ...)
567 local datasource = {}
568 datasource.config = "table"
571 function datasource.get(self, section, option)
572 return data[section] and data[section][option]
575 function datasource.del(...)
579 function datasource.get_scheme()
583 AbstractSection.__init__(self, datasource, "table", ...)
584 self.template = "cbi/tblsection"
585 self.rowcolors = true
586 self.anonymous = true
589 function Table.parse(self)
590 for i, k in ipairs(self:cfgsections()) do
591 if luci.http.formvalue("cbi.submit") then
597 function Table.cfgsections(self)
600 for i, v in luci.util.kspairs(self.data) do
601 table.insert(sections, i)
610 NamedSection - A fixed configuration section defined by its name
612 NamedSection = class(AbstractSection)
614 function NamedSection.__init__(self, map, section, stype, ...)
615 AbstractSection.__init__(self, map, stype, ...)
616 Node._i18n(self, map.config, section, nil, ...)
619 self.addremove = false
621 -- Use defaults from UVL
622 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
623 local vs = self.map:get_scheme(self.sectiontype)
624 self.addremove = not vs.unique and not vs.required
625 self.dynamic = vs.dynamic
626 self.title = self.title or vs.title
627 self.description = self.description or vs.descr
630 self.template = "cbi/nsection"
631 self.section = section
634 function NamedSection.parse(self)
635 local s = self.section
636 local active = self:cfgvalue(s)
638 if self.addremove then
639 local path = self.config.."."..s
640 if active then -- Remove the section
641 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
644 else -- Create and apply default values
645 if luci.http.formvalue("cbi.cns."..path) then
653 AbstractSection.parse_dynamic(self, s)
654 if luci.http.formvalue("cbi.submit") then
657 if not self.override_scheme and self.map.scheme then
658 _uvl_validate_section(self, s)
661 AbstractSection.parse_optionals(self, s)
667 TypedSection - A (set of) configuration section(s) defined by the type
668 addremove: Defines whether the user can add/remove sections of this type
669 anonymous: Allow creating anonymous sections
670 validate: a validation function returning nil if the section is invalid
672 TypedSection = class(AbstractSection)
674 function TypedSection.__init__(self, map, type, ...)
675 AbstractSection.__init__(self, map, type, ...)
676 Node._i18n(self, map.config, type, nil, ...)
678 self.template = "cbi/tsection"
680 self.anonymous = false
682 -- Use defaults from UVL
683 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
684 local vs = self.map:get_scheme(self.sectiontype)
685 self.addremove = not vs.unique and not vs.required
686 self.dynamic = vs.dynamic
687 self.anonymous = not vs.named
688 self.title = self.title or vs.title
689 self.description = self.description or vs.descr
693 -- Return all matching UCI sections for this TypedSection
694 function TypedSection.cfgsections(self)
696 self.map.uci:foreach(self.map.config, self.sectiontype,
698 if self:checkscope(section[".name"]) then
699 table.insert(sections, section[".name"])
706 -- Limits scope to sections that have certain option => value pairs
707 function TypedSection.depends(self, option, value)
708 table.insert(self.deps, {option=option, value=value})
711 function TypedSection.parse(self)
712 if self.addremove then
714 local crval = REMOVE_PREFIX .. self.config
715 local name = luci.http.formvaluetable(crval)
716 for k,v in pairs(name) do
717 if self:cfgvalue(k) and self:checkscope(k) then
724 for i, k in ipairs(self:cfgsections()) do
725 AbstractSection.parse_dynamic(self, k)
726 if luci.http.formvalue("cbi.submit") then
729 if not self.override_scheme and self.map.scheme then
730 _uvl_validate_section(self, k)
733 AbstractSection.parse_optionals(self, k)
736 if self.addremove then
738 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
739 local name = luci.http.formvalue(crval)
740 if self.anonymous then
746 -- Ignore if it already exists
747 if self:cfgvalue(name) then
751 name = self:checkscope(name)
754 self.err_invalid = true
757 if name and #name > 0 then
765 -- Verifies scope of sections
766 function TypedSection.checkscope(self, section)
767 -- Check if we are not excluded
768 if self.filter and not self:filter(section) then
772 -- Check if at least one dependency is met
773 if #self.deps > 0 and self:cfgvalue(section) then
776 for k, v in ipairs(self.deps) do
777 if self:cfgvalue(section)[v.option] == v.value then
787 return self:validate(section)
791 -- Dummy validate function
792 function TypedSection.validate(self, section)
798 AbstractValue - An abstract Value Type
799 null: Value can be empty
800 valid: A function returning the value if it is valid otherwise nil
801 depends: A table of option => value pairs of which one must be true
802 default: The default value
803 size: The size of the input fields
804 rmempty: Unset value if empty
805 optional: This value is optional (see AbstractSection.optionals)
807 AbstractValue = class(Node)
809 function AbstractValue.__init__(self, map, section, option, ...)
810 Node.__init__(self, ...)
811 self.section = section
814 self.config = map.config
815 self.tag_invalid = {}
816 self.tag_missing = {}
817 self.tag_reqerror = {}
822 self.track_missing = false
826 self.optional = false
828 -- Use defaults from UVL
829 if not self.override_scheme
830 and self.map:get_scheme(self.section.sectiontype, self.option) then
831 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
832 self.rmempty = not vs.required
833 self.cast = (vs.type == "list") and "list" or "string"
834 self.title = self.title or vs.title
835 self.description = self.description or vs.descr
837 if vs.depends and not self.override_dependencies then
838 for i, deps in ipairs(vs.depends) do
839 deps = _uvl_strip_remote_dependencies(deps)
848 -- Add a dependencie to another section field
849 function AbstractValue.depends(self, field, value)
851 if type(field) == "string" then
858 table.insert(self.deps, {deps=deps, add=""})
861 -- Generates the unique CBID
862 function AbstractValue.cbid(self, section)
863 return "cbid."..self.map.config.."."..section.."."..self.option
866 -- Return whether this object should be created
867 function AbstractValue.formcreated(self, section)
868 local key = "cbi.opt."..self.config.."."..section
869 return (luci.http.formvalue(key) == self.option)
872 -- Returns the formvalue for this object
873 function AbstractValue.formvalue(self, section)
874 return luci.http.formvalue(self:cbid(section))
877 function AbstractValue.additional(self, value)
878 self.optional = value
881 function AbstractValue.mandatory(self, value)
882 self.rmempty = not value
885 function AbstractValue.parse(self, section)
886 local fvalue = self:formvalue(section)
887 local cvalue = self:cfgvalue(section)
889 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
890 fvalue = self:transform(self:validate(fvalue, section))
892 self.tag_invalid[section] = true
894 if fvalue and not (fvalue == cvalue) then
895 self:write(section, fvalue)
897 else -- Unset the UCI or error
898 if self.rmempty or self.optional then
900 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
901 self.tag_missing[section] = true
906 -- Render if this value exists or if it is mandatory
907 function AbstractValue.render(self, s, scope)
908 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
911 scope.cbid = self:cbid(s)
912 scope.striptags = luci.util.striptags
914 scope.ifattr = function(cond,key,val)
916 return string.format(
917 ' %s="%s"', tostring(key),
918 luci.util.pcdata(tostring( val
920 or (type(self[key]) ~= "function" and self[key])
928 scope.attr = function(...)
929 return scope.ifattr( true, ... )
932 Node.render(self, scope)
936 -- Return the UCI value of this object
937 function AbstractValue.cfgvalue(self, section)
938 local value = self.map:get(section, self.option)
939 if not self.cast or self.cast == type(value) then
941 elseif self.cast == "string" then
942 if type(value) == "table" then
945 elseif self.cast == "table" then
950 -- Validate the form value
951 function AbstractValue.validate(self, value)
955 AbstractValue.transform = AbstractValue.validate
959 function AbstractValue.write(self, section, value)
960 return self.map:set(section, self.option, value)
964 function AbstractValue.remove(self, section)
965 return self.map:del(section, self.option)
972 Value - A one-line value
973 maxlength: The maximum length
975 Value = class(AbstractValue)
977 function Value.__init__(self, ...)
978 AbstractValue.__init__(self, ...)
979 self.template = "cbi/value"
984 function Value.value(self, key, val)
986 table.insert(self.keylist, tostring(key))
987 table.insert(self.vallist, tostring(val))
991 -- DummyValue - This does nothing except being there
992 DummyValue = class(AbstractValue)
994 function DummyValue.__init__(self, ...)
995 AbstractValue.__init__(self, ...)
996 self.template = "cbi/dvalue"
1000 function DummyValue.parse(self)
1006 Flag - A flag being enabled or disabled
1008 Flag = class(AbstractValue)
1010 function Flag.__init__(self, ...)
1011 AbstractValue.__init__(self, ...)
1012 self.template = "cbi/fvalue"
1018 -- A flag can only have two states: set or unset
1019 function Flag.parse(self, section)
1020 local fvalue = self:formvalue(section)
1023 fvalue = self.enabled
1025 fvalue = self.disabled
1028 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1029 if not(fvalue == self:cfgvalue(section)) then
1030 self:write(section, fvalue)
1033 self:remove(section)
1040 ListValue - A one-line value predefined in a list
1041 widget: The widget that will be used (select, radio)
1043 ListValue = class(AbstractValue)
1045 function ListValue.__init__(self, ...)
1046 AbstractValue.__init__(self, ...)
1047 self.template = "cbi/lvalue"
1052 self.widget = "select"
1054 if not self.override_scheme
1055 and self.map:get_scheme(self.section.sectiontype, self.option) then
1056 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1057 if self.value and vs.values and not self.override_values then
1058 if self.rmempty or self.optional then
1061 for k, v in pairs(vs.values) do
1063 if not self.override_dependencies
1064 and vs.enum_depends and vs.enum_depends[k] then
1065 for i, dep in ipairs(vs.enum_depends[k]) do
1066 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1069 self:value(k, v, unpack(deps))
1075 function ListValue.value(self, key, val, ...)
1076 if luci.util.contains(self.keylist, key) then
1081 table.insert(self.keylist, tostring(key))
1082 table.insert(self.vallist, tostring(val))
1084 for i, deps in ipairs({...}) do
1085 table.insert(self.deps, {add = "-"..key, deps=deps})
1089 function ListValue.validate(self, val)
1090 if luci.util.contains(self.keylist, val) then
1100 MultiValue - Multiple delimited values
1101 widget: The widget that will be used (select, checkbox)
1102 delimiter: The delimiter that will separate the values (default: " ")
1104 MultiValue = class(AbstractValue)
1106 function MultiValue.__init__(self, ...)
1107 AbstractValue.__init__(self, ...)
1108 self.template = "cbi/mvalue"
1113 self.widget = "checkbox"
1114 self.delimiter = " "
1117 function MultiValue.render(self, ...)
1118 if self.widget == "select" and not self.size then
1119 self.size = #self.vallist
1122 AbstractValue.render(self, ...)
1125 function MultiValue.value(self, key, val)
1126 if luci.util.contains(self.keylist, key) then
1131 table.insert(self.keylist, tostring(key))
1132 table.insert(self.vallist, tostring(val))
1135 function MultiValue.valuelist(self, section)
1136 local val = self:cfgvalue(section)
1138 if not(type(val) == "string") then
1142 return luci.util.split(val, self.delimiter)
1145 function MultiValue.validate(self, val)
1146 val = (type(val) == "table") and val or {val}
1150 for i, value in ipairs(val) do
1151 if luci.util.contains(self.keylist, value) then
1152 result = result and (result .. self.delimiter .. value) or value
1160 StaticList = class(MultiValue)
1162 function StaticList.__init__(self, ...)
1163 MultiValue.__init__(self, ...)
1165 self.valuelist = self.cfgvalue
1167 if not self.override_scheme
1168 and self.map:get_scheme(self.section.sectiontype, self.option) then
1169 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1170 if self.value and vs.values and not self.override_values then
1171 for k, v in pairs(vs.values) do
1178 function StaticList.validate(self, value)
1179 value = (type(value) == "table") and value or {value}
1182 for i, v in ipairs(value) do
1183 if luci.util.contains(self.valuelist, v) then
1184 table.insert(valid, v)
1191 DynamicList = class(AbstractValue)
1193 function DynamicList.__init__(self, ...)
1194 AbstractValue.__init__(self, ...)
1195 self.template = "cbi/dynlist"
1201 function DynamicList.value(self, key, val)
1203 table.insert(self.keylist, tostring(key))
1204 table.insert(self.vallist, tostring(val))
1207 function DynamicList.validate(self, value, section)
1208 value = (type(value) == "table") and value or {value}
1211 for i, v in ipairs(value) do
1213 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1214 table.insert(valid, v)
1223 TextValue - A multi-line value
1226 TextValue = class(AbstractValue)
1228 function TextValue.__init__(self, ...)
1229 AbstractValue.__init__(self, ...)
1230 self.template = "cbi/tvalue"
1236 Button = class(AbstractValue)
1238 function Button.__init__(self, ...)
1239 AbstractValue.__init__(self, ...)
1240 self.template = "cbi/button"
1241 self.inputstyle = nil