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")
58 luci.i18n.loadc("cbi")
60 luci.util.resfenv(func)
61 luci.util.updfenv(func, luci.cbi)
62 luci.util.extfenv(func, "translate", luci.i18n.translate)
63 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
64 luci.util.extfenv(func, "arg", {...})
68 for i, map in ipairs(maps) do
69 if not instanceof(map, Node) then
70 error("CBI map returns no valid map object!")
78 local function _uvl_validate_section(node, name)
79 local co = node.map:get()
81 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
82 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
84 local stat, err = node.map.validator:validate_section(node.config, name, co)
87 if err:is(luci.uvl.errors.ERR_DEPENDENCY) then
88 node.tag_deperror[name] = true
90 node.tag_invalid[name] = true
92 for i, v in ipairs(err.childs) do
93 if v.option and node.fields[v.option] then
94 if v:is(luci.uvl.errors.ERR_DEPENDENCY) then
95 node.fields[v.option].tag_reqerror[name] = true
96 elseif v:is(luci.uvl.errors.ERR_OPT_REQUIRED) then
97 node.fields[v.option].tag_missing[name] = true
98 node.tag_deperror[name] = true
99 elseif v:is(luci.uvl.errors.ERR_OPTION) then
100 node.fields[v.option].tag_invalid[name] = true
108 local function _uvl_strip_remote_dependencies(deps)
111 for k, v in pairs(deps) do
112 k = k:gsub("%$config%.%$section%.", "")
113 if k:match("^[%w_]+$") and type(v) == "string" then
122 -- Node pseudo abstract class
125 function Node.__init__(self, title, description)
127 self.title = title or ""
128 self.description = description or ""
129 self.template = "cbi/node"
133 function Node._i18n(self, config, section, option, title, description)
136 if type(luci.i18n) == "table" then
138 local key = config and config:gsub("[^%w]+", "") or ""
140 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
141 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
143 self.title = title or luci.i18n.translate( key, option or section or config )
144 self.description = description or luci.i18n.translate( key .. "_desc", "" )
148 -- Append child nodes
149 function Node.append(self, obj)
150 table.insert(self.children, obj)
153 -- Parse this node and its children
154 function Node.parse(self, ...)
155 for k, child in ipairs(self.children) do
161 function Node.render(self, scope)
165 luci.template.render(self.template, scope)
168 -- Render the children
169 function Node.render_children(self, ...)
170 for k, node in ipairs(self.children) do
177 A simple template element
179 Template = class(Node)
181 function Template.__init__(self, template)
183 self.template = template
186 function Template.render(self)
187 luci.template.render(self.template, {self=self})
192 Map - A map describing a configuration file
196 function Map.__init__(self, config, ...)
197 Node.__init__(self, ...)
198 Node._i18n(self, config, nil, nil, ...)
201 self.parsechain = {self.config}
202 self.template = "cbi/map"
203 self.uci = uci.cursor()
205 if not self.uci:load(self.config) then
206 error("Unable to read UCI data: " .. self.config)
209 self.validator = luci.uvl.UVL()
210 self.scheme = self.validator:get_scheme(self.config)
214 function Map.get_scheme(self, sectiontype, option)
216 return self.scheme and self.scheme.sections[sectiontype]
218 return self.scheme and self.scheme.variables[sectiontype]
219 and self.scheme.variables[sectiontype][option]
224 -- Chain foreign config
225 function Map.chain(self, config)
226 table.insert(self.parsechain, config)
229 -- Use optimized UCI writing
230 function Map.parse(self, ...)
231 Node.parse(self, ...)
234 for i, config in ipairs(self.parsechain) do
235 self.uci:save(config)
237 if luci.http.formvalue("cbi.apply") then
238 for i, config in ipairs(self.parsechain) do
239 self.uci:commit(config)
240 self.uci:apply(config)
242 -- Refresh data because commit changes section names
243 self.uci:load(config)
247 Node.parse(self, ...)
250 for i, config in ipairs(self.parsechain) do
251 self.uci:unload(config)
256 -- Creates a child section
257 function Map.section(self, class, ...)
258 if instanceof(class, AbstractSection) then
259 local obj = class(self, ...)
263 error("class must be a descendent of AbstractSection")
268 function Map.add(self, sectiontype)
269 return self.uci:add(self.config, sectiontype)
273 function Map.set(self, section, option, value)
275 return self.uci:set(self.config, section, option, value)
277 return self.uci:set(self.config, section, value)
282 function Map.del(self, section, option)
284 return self.uci:delete(self.config, section, option)
286 return self.uci:delete(self.config, section)
291 function Map.get(self, section, option)
293 return self.uci:get_all(self.config)
295 return self.uci:get(self.config, section, option)
297 return self.uci:get_all(self.config, section)
307 Page.__init__ = Node.__init__
308 Page.parse = function() end
312 SimpleForm - A Simple non-UCI form
314 SimpleForm = class(Node)
316 function SimpleForm.__init__(self, config, title, description, data)
317 Node.__init__(self, title, description)
319 self.data = data or {}
320 self.template = "cbi/simpleform"
324 function SimpleForm.parse(self, ...)
325 if luci.http.formvalue("cbi.submit") then
326 Node.parse(self, 1, ...)
330 for k, j in ipairs(self.children) do
331 for i, v in ipairs(j.children) do
333 and (not v.tag_missing or not v.tag_missing[1])
334 and (not v.tag_invalid or not v.tag_invalid[1])
339 not luci.http.formvalue("cbi.submit") and 0
343 self.dorender = not self.handle or self:handle(state, self.data) ~= false
346 function SimpleForm.render(self, ...)
347 if self.dorender then
348 Node.render(self, ...)
352 function SimpleForm.section(self, class, ...)
353 if instanceof(class, AbstractSection) then
354 local obj = class(self, ...)
358 error("class must be a descendent of AbstractSection")
362 -- Creates a child field
363 function SimpleForm.field(self, class, ...)
365 for k, v in ipairs(self.children) do
366 if instanceof(v, SimpleSection) then
372 section = self:section(SimpleSection)
375 if instanceof(class, AbstractValue) then
376 local obj = class(self, section, ...)
377 obj.track_missing = true
381 error("class must be a descendent of AbstractValue")
385 function SimpleForm.set(self, section, option, value)
386 self.data[option] = value
390 function SimpleForm.del(self, section, option)
391 self.data[option] = nil
395 function SimpleForm.get(self, section, option)
396 return self.data[option]
400 function SimpleForm.get_scheme()
409 AbstractSection = class(Node)
411 function AbstractSection.__init__(self, map, sectiontype, ...)
412 Node.__init__(self, ...)
413 self.sectiontype = sectiontype
415 self.config = map.config
420 self.tag_invalid = {}
421 self.tag_deperror = {}
424 self.addremove = false
428 -- Appends a new option
429 function AbstractSection.option(self, class, option, ...)
430 -- Autodetect from UVL
431 if class == true and self.map:get_scheme(self.sectiontype, option) then
432 local vs = self.map:get_scheme(self.sectiontype, option)
433 if vs.type == "boolean" then
435 elseif vs.type == "list" then
437 elseif vs.type == "enum" or vs.type == "reference" then
444 if instanceof(class, AbstractValue) then
445 local obj = class(self.map, self, option, ...)
447 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
450 self.fields[option] = obj
452 elseif class == true then
453 error("No valid class was given and autodetection failed.")
455 error("class must be a descendant of AbstractValue")
459 -- Parse optional options
460 function AbstractSection.parse_optionals(self, section)
461 if not self.optional then
465 self.optionals[section] = {}
467 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
468 for k,v in ipairs(self.children) do
469 if v.optional and not v:cfgvalue(section) then
470 if field == v.option then
473 table.insert(self.optionals[section], v)
478 if field and #field > 0 and self.dynamic then
479 self:add_dynamic(field)
483 -- Add a dynamic option
484 function AbstractSection.add_dynamic(self, field, optional)
485 local o = self:option(Value, field, field)
486 o.optional = optional
489 -- Parse all dynamic options
490 function AbstractSection.parse_dynamic(self, section)
491 if not self.dynamic then
495 local arr = luci.util.clone(self:cfgvalue(section))
496 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
497 for k, v in pairs(form) do
501 for key,val in pairs(arr) do
504 for i,c in ipairs(self.children) do
505 if c.option == key then
510 if create and key:sub(1, 1) ~= "." then
511 self:add_dynamic(key, true)
516 -- Returns the section's UCI table
517 function AbstractSection.cfgvalue(self, section)
518 return self.map:get(section)
521 -- Removes the section
522 function AbstractSection.remove(self, section)
523 return self.map:del(section)
526 -- Creates the section
527 function AbstractSection.create(self, section)
531 stat = self.map:set(section, nil, self.sectiontype)
533 section = self.map:add(self.sectiontype)
538 for k,v in pairs(self.children) do
540 self.map:set(section, v.option, v.default)
544 for k,v in pairs(self.defaults) do
545 self.map:set(section, k, v)
553 SimpleSection = class(AbstractSection)
555 function SimpleSection.__init__(self, form, ...)
556 AbstractSection.__init__(self, form, nil, ...)
557 self.template = "cbi/nullsection"
561 Table = class(AbstractSection)
563 function Table.__init__(self, form, data, ...)
564 local datasource = {}
565 datasource.config = "table"
568 function datasource.get(self, section, option)
569 return data[section] and data[section][option]
572 function datasource.del(...)
576 function datasource.get_scheme()
580 AbstractSection.__init__(self, datasource, "table", ...)
581 self.template = "cbi/tblsection"
582 self.rowcolors = true
583 self.anonymous = true
586 function Table.parse(self)
587 for i, k in ipairs(self:cfgsections()) do
588 if luci.http.formvalue("cbi.submit") then
594 function Table.cfgsections(self)
597 for i, v in luci.util.kspairs(self.data) do
598 table.insert(sections, i)
607 NamedSection - A fixed configuration section defined by its name
609 NamedSection = class(AbstractSection)
611 function NamedSection.__init__(self, map, section, stype, ...)
612 AbstractSection.__init__(self, map, stype, ...)
613 Node._i18n(self, map.config, section, nil, ...)
616 self.addremove = false
618 -- Use defaults from UVL
619 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
620 local vs = self.map:get_scheme(self.sectiontype)
621 self.addremove = not vs.unique and not vs.required
622 self.dynamic = vs.dynamic
623 self.title = self.title or vs.title
624 self.description = self.description or vs.descr
627 self.template = "cbi/nsection"
628 self.section = section
631 function NamedSection.parse(self)
632 local s = self.section
633 local active = self:cfgvalue(s)
635 if self.addremove then
636 local path = self.config.."."..s
637 if active then -- Remove the section
638 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
641 else -- Create and apply default values
642 if luci.http.formvalue("cbi.cns."..path) then
650 AbstractSection.parse_dynamic(self, s)
651 if luci.http.formvalue("cbi.submit") then
654 if not self.override_scheme and self.map.scheme then
655 _uvl_validate_section(self, s)
658 AbstractSection.parse_optionals(self, s)
664 TypedSection - A (set of) configuration section(s) defined by the type
665 addremove: Defines whether the user can add/remove sections of this type
666 anonymous: Allow creating anonymous sections
667 validate: a validation function returning nil if the section is invalid
669 TypedSection = class(AbstractSection)
671 function TypedSection.__init__(self, map, type, ...)
672 AbstractSection.__init__(self, map, type, ...)
673 Node._i18n(self, map.config, type, nil, ...)
675 self.template = "cbi/tsection"
677 self.anonymous = false
679 -- Use defaults from UVL
680 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
681 local vs = self.map:get_scheme(self.sectiontype)
682 self.addremove = not vs.unique and not vs.required
683 self.dynamic = vs.dynamic
684 self.anonymous = not vs.named
685 self.title = self.title or vs.title
686 self.description = self.description or vs.descr
690 -- Return all matching UCI sections for this TypedSection
691 function TypedSection.cfgsections(self)
693 self.map.uci:foreach(self.map.config, self.sectiontype,
695 if self:checkscope(section[".name"]) then
696 table.insert(sections, section[".name"])
703 -- Limits scope to sections that have certain option => value pairs
704 function TypedSection.depends(self, option, value)
705 table.insert(self.deps, {option=option, value=value})
708 function TypedSection.parse(self)
709 if self.addremove then
711 local crval = REMOVE_PREFIX .. self.config
712 local name = luci.http.formvaluetable(crval)
713 for k,v in pairs(name) do
714 if self:cfgvalue(k) and self:checkscope(k) then
721 for i, k in ipairs(self:cfgsections()) do
722 AbstractSection.parse_dynamic(self, k)
723 if luci.http.formvalue("cbi.submit") then
726 if not self.override_scheme and self.map.scheme then
727 _uvl_validate_section(self, k)
730 AbstractSection.parse_optionals(self, k)
733 if self.addremove then
736 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
737 local name = luci.http.formvalue(crval)
738 if self.anonymous then
740 created = self:create()
744 -- Ignore if it already exists
745 if self:cfgvalue(name) then
749 name = self:checkscope(name)
752 self.err_invalid = true
755 if name and #name > 0 then
756 created = self:create(name) and name
762 AbstractSection.parse_optionals(self, created)
767 -- Verifies scope of sections
768 function TypedSection.checkscope(self, section)
769 -- Check if we are not excluded
770 if self.filter and not self:filter(section) then
774 -- Check if at least one dependency is met
775 if #self.deps > 0 and self:cfgvalue(section) then
778 for k, v in ipairs(self.deps) do
779 if self:cfgvalue(section)[v.option] == v.value then
789 return self:validate(section)
793 -- Dummy validate function
794 function TypedSection.validate(self, section)
800 AbstractValue - An abstract Value Type
801 null: Value can be empty
802 valid: A function returning the value if it is valid otherwise nil
803 depends: A table of option => value pairs of which one must be true
804 default: The default value
805 size: The size of the input fields
806 rmempty: Unset value if empty
807 optional: This value is optional (see AbstractSection.optionals)
809 AbstractValue = class(Node)
811 function AbstractValue.__init__(self, map, section, option, ...)
812 Node.__init__(self, ...)
813 self.section = section
816 self.config = map.config
817 self.tag_invalid = {}
818 self.tag_missing = {}
819 self.tag_reqerror = {}
824 self.track_missing = false
828 self.optional = false
830 -- Use defaults from UVL
831 if not self.override_scheme
832 and self.map:get_scheme(self.section.sectiontype, self.option) then
833 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
834 self.rmempty = not vs.required
835 self.cast = (vs.type == "list") and "list" or "string"
836 self.title = self.title or vs.title
837 self.description = self.description or vs.descr
839 if vs.depends and not self.override_dependencies then
840 for i, deps in ipairs(vs.depends) do
841 deps = _uvl_strip_remote_dependencies(deps)
850 -- Add a dependencie to another section field
851 function AbstractValue.depends(self, field, value)
853 if type(field) == "string" then
860 table.insert(self.deps, {deps=deps, add=""})
863 -- Generates the unique CBID
864 function AbstractValue.cbid(self, section)
865 return "cbid."..self.map.config.."."..section.."."..self.option
868 -- Return whether this object should be created
869 function AbstractValue.formcreated(self, section)
870 local key = "cbi.opt."..self.config.."."..section
871 return (luci.http.formvalue(key) == self.option)
874 -- Returns the formvalue for this object
875 function AbstractValue.formvalue(self, section)
876 return luci.http.formvalue(self:cbid(section))
879 function AbstractValue.additional(self, value)
880 self.optional = value
883 function AbstractValue.mandatory(self, value)
884 self.rmempty = not value
887 function AbstractValue.parse(self, section)
888 local fvalue = self:formvalue(section)
889 local cvalue = self:cfgvalue(section)
891 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
892 fvalue = self:transform(self:validate(fvalue, section))
894 self.tag_invalid[section] = true
896 if fvalue and not (fvalue == cvalue) then
897 self:write(section, fvalue)
899 else -- Unset the UCI or error
900 if self.rmempty or self.optional then
902 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
903 self.tag_missing[section] = true
908 -- Render if this value exists or if it is mandatory
909 function AbstractValue.render(self, s, scope)
910 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
913 scope.cbid = self:cbid(s)
914 scope.striptags = luci.util.striptags
916 scope.ifattr = function(cond,key,val)
918 return string.format(
919 ' %s="%s"', tostring(key),
920 luci.util.pcdata(tostring( val
922 or (type(self[key]) ~= "function" and self[key])
930 scope.attr = function(...)
931 return scope.ifattr( true, ... )
934 Node.render(self, scope)
938 -- Return the UCI value of this object
939 function AbstractValue.cfgvalue(self, section)
940 local value = self.map:get(section, self.option)
941 if not self.cast or self.cast == type(value) then
943 elseif self.cast == "string" then
944 if type(value) == "table" then
947 elseif self.cast == "table" then
952 -- Validate the form value
953 function AbstractValue.validate(self, value)
957 AbstractValue.transform = AbstractValue.validate
961 function AbstractValue.write(self, section, value)
962 return self.map:set(section, self.option, value)
966 function AbstractValue.remove(self, section)
967 return self.map:del(section, self.option)
974 Value - A one-line value
975 maxlength: The maximum length
977 Value = class(AbstractValue)
979 function Value.__init__(self, ...)
980 AbstractValue.__init__(self, ...)
981 self.template = "cbi/value"
986 function Value.value(self, key, val)
988 table.insert(self.keylist, tostring(key))
989 table.insert(self.vallist, tostring(val))
993 -- DummyValue - This does nothing except being there
994 DummyValue = class(AbstractValue)
996 function DummyValue.__init__(self, ...)
997 AbstractValue.__init__(self, ...)
998 self.template = "cbi/dvalue"
1002 function DummyValue.parse(self)
1008 Flag - A flag being enabled or disabled
1010 Flag = class(AbstractValue)
1012 function Flag.__init__(self, ...)
1013 AbstractValue.__init__(self, ...)
1014 self.template = "cbi/fvalue"
1020 -- A flag can only have two states: set or unset
1021 function Flag.parse(self, section)
1022 local fvalue = self:formvalue(section)
1025 fvalue = self.enabled
1027 fvalue = self.disabled
1030 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1031 if not(fvalue == self:cfgvalue(section)) then
1032 self:write(section, fvalue)
1035 self:remove(section)
1042 ListValue - A one-line value predefined in a list
1043 widget: The widget that will be used (select, radio)
1045 ListValue = class(AbstractValue)
1047 function ListValue.__init__(self, ...)
1048 AbstractValue.__init__(self, ...)
1049 self.template = "cbi/lvalue"
1054 self.widget = "select"
1056 if not self.override_scheme
1057 and self.map:get_scheme(self.section.sectiontype, self.option) then
1058 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1059 if self.value and vs.values and not self.override_values then
1060 if self.rmempty or self.optional then
1063 for k, v in pairs(vs.values) do
1065 if not self.override_dependencies
1066 and vs.enum_depends and vs.enum_depends[k] then
1067 for i, dep in ipairs(vs.enum_depends[k]) do
1068 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1071 self:value(k, v, unpack(deps))
1077 function ListValue.value(self, key, val, ...)
1078 if luci.util.contains(self.keylist, key) then
1083 table.insert(self.keylist, tostring(key))
1084 table.insert(self.vallist, tostring(val))
1086 for i, deps in ipairs({...}) do
1087 table.insert(self.deps, {add = "-"..key, deps=deps})
1091 function ListValue.validate(self, val)
1092 if luci.util.contains(self.keylist, val) then
1102 MultiValue - Multiple delimited values
1103 widget: The widget that will be used (select, checkbox)
1104 delimiter: The delimiter that will separate the values (default: " ")
1106 MultiValue = class(AbstractValue)
1108 function MultiValue.__init__(self, ...)
1109 AbstractValue.__init__(self, ...)
1110 self.template = "cbi/mvalue"
1115 self.widget = "checkbox"
1116 self.delimiter = " "
1119 function MultiValue.render(self, ...)
1120 if self.widget == "select" and not self.size then
1121 self.size = #self.vallist
1124 AbstractValue.render(self, ...)
1127 function MultiValue.value(self, key, val)
1128 if luci.util.contains(self.keylist, key) then
1133 table.insert(self.keylist, tostring(key))
1134 table.insert(self.vallist, tostring(val))
1137 function MultiValue.valuelist(self, section)
1138 local val = self:cfgvalue(section)
1140 if not(type(val) == "string") then
1144 return luci.util.split(val, self.delimiter)
1147 function MultiValue.validate(self, val)
1148 val = (type(val) == "table") and val or {val}
1152 for i, value in ipairs(val) do
1153 if luci.util.contains(self.keylist, value) then
1154 result = result and (result .. self.delimiter .. value) or value
1162 StaticList = class(MultiValue)
1164 function StaticList.__init__(self, ...)
1165 MultiValue.__init__(self, ...)
1167 self.valuelist = self.cfgvalue
1169 if not self.override_scheme
1170 and self.map:get_scheme(self.section.sectiontype, self.option) then
1171 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1172 if self.value and vs.values and not self.override_values then
1173 for k, v in pairs(vs.values) do
1180 function StaticList.validate(self, value)
1181 value = (type(value) == "table") and value or {value}
1184 for i, v in ipairs(value) do
1185 if luci.util.contains(self.valuelist, v) then
1186 table.insert(valid, v)
1193 DynamicList = class(AbstractValue)
1195 function DynamicList.__init__(self, ...)
1196 AbstractValue.__init__(self, ...)
1197 self.template = "cbi/dynlist"
1203 function DynamicList.value(self, key, val)
1205 table.insert(self.keylist, tostring(key))
1206 table.insert(self.vallist, tostring(val))
1209 function DynamicList.validate(self, value, section)
1210 value = (type(value) == "table") and value or {value}
1213 for i, v in ipairs(value) do
1215 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1216 table.insert(valid, v)
1225 TextValue - A multi-line value
1228 TextValue = class(AbstractValue)
1230 function TextValue.__init__(self, ...)
1231 AbstractValue.__init__(self, ...)
1232 self.template = "cbi/tvalue"
1238 Button = class(AbstractValue)
1240 function Button.__init__(self, ...)
1241 AbstractValue.__init__(self, ...)
1242 self.template = "cbi/button"
1243 self.inputstyle = nil