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!")
82 function _uvl_strip_remote_dependencies(deps)
85 for k, v in pairs(deps) do
86 k = k:gsub("%$config%.%$section%.", "")
87 if k:match("^[%w_]+$") and type(v) == "string" then
96 -- Node pseudo abstract class
99 function Node.__init__(self, title, description)
101 self.title = title or ""
102 self.description = description or ""
103 self.template = "cbi/node"
107 function Node._i18n(self, config, section, option, title, description)
110 if type(luci.i18n) == "table" then
112 local key = config and config:gsub("[^%w]+", "") or ""
114 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
115 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
117 self.title = title or luci.i18n.translate( key, option or section or config )
118 self.description = description or luci.i18n.translate( key .. "_desc", "" )
122 -- Append child nodes
123 function Node.append(self, obj)
124 table.insert(self.children, obj)
127 -- Parse this node and its children
128 function Node.parse(self, ...)
129 for k, child in ipairs(self.children) do
135 function Node.render(self, scope)
139 luci.template.render(self.template, scope)
142 -- Render the children
143 function Node.render_children(self, ...)
144 for k, node in ipairs(self.children) do
151 A simple template element
153 Template = class(Node)
155 function Template.__init__(self, template)
157 self.template = template
160 function Template.render(self)
161 luci.template.render(self.template, {self=self})
166 Map - A map describing a configuration file
170 function Map.__init__(self, config, ...)
171 Node.__init__(self, ...)
172 Node._i18n(self, config, nil, nil, ...)
175 self.parsechain = {self.config}
176 self.template = "cbi/map"
177 self.uci = uci.cursor()
178 if not self.uci:load(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]
196 -- Chain foreign config
197 function Map.chain(self, config)
198 table.insert(self.parsechain, config)
201 -- Use optimized UCI writing
202 function Map.parse(self, ...)
203 Node.parse(self, ...)
205 for i, config in ipairs(self.parsechain) do
206 self.uci:save(config)
208 if luci.http.formvalue("cbi.apply") then
209 for i, config in ipairs(self.parsechain) do
210 self.uci:commit(config)
211 self.uci:apply(config)
213 -- Refresh data because commit changes section names
214 self.uci:load(config)
218 Node.parse(self, ...)
221 for i, config in ipairs(self.parsechain) do
222 self.uci:unload(config)
226 -- Creates a child section
227 function Map.section(self, class, ...)
228 if instanceof(class, AbstractSection) then
229 local obj = class(self, ...)
233 error("class must be a descendent of AbstractSection")
238 function Map.add(self, sectiontype)
239 return self.uci:add(self.config, sectiontype)
243 function Map.set(self, section, option, value)
245 return self.uci:set(self.config, section, option, value)
247 return self.uci:set(self.config, section, value)
252 function Map.del(self, section, option)
254 return self.uci:delete(self.config, section, option)
256 return self.uci:delete(self.config, section)
261 function Map.get(self, section, option)
263 return self.uci:get_all(self.config)
265 return self.uci:get(self.config, section, option)
267 return self.uci:get_all(self.config, section)
277 Page.__init__ = Node.__init__
278 Page.parse = function() end
282 SimpleForm - A Simple non-UCI form
284 SimpleForm = class(Node)
286 function SimpleForm.__init__(self, config, title, description, data)
287 Node.__init__(self, title, description)
289 self.data = data or {}
290 self.template = "cbi/simpleform"
294 function SimpleForm.parse(self, ...)
295 if luci.http.formvalue("cbi.submit") then
296 Node.parse(self, 1, ...)
300 for k, j in ipairs(self.children) do
301 for i, v in ipairs(j.children) do
303 and (not v.tag_missing or not v.tag_missing[1])
304 and (not v.tag_invalid or not v.tag_invalid[1])
309 not luci.http.formvalue("cbi.submit") and 0
313 self.dorender = not self.handle or self:handle(state, self.data) ~= false
316 function SimpleForm.render(self, ...)
317 if self.dorender then
318 Node.render(self, ...)
322 function SimpleForm.section(self, class, ...)
323 if instanceof(class, AbstractSection) then
324 local obj = class(self, ...)
328 error("class must be a descendent of AbstractSection")
332 -- Creates a child field
333 function SimpleForm.field(self, class, ...)
335 for k, v in ipairs(self.children) do
336 if instanceof(v, SimpleSection) then
342 section = self:section(SimpleSection)
345 if instanceof(class, AbstractValue) then
346 local obj = class(self, section, ...)
347 obj.track_missing = true
351 error("class must be a descendent of AbstractValue")
355 function SimpleForm.set(self, section, option, value)
356 self.data[option] = value
360 function SimpleForm.del(self, section, option)
361 self.data[option] = nil
365 function SimpleForm.get(self, section, option)
366 return self.data[option]
370 function SimpleForm.get_scheme()
379 AbstractSection = class(Node)
381 function AbstractSection.__init__(self, map, sectiontype, ...)
382 Node.__init__(self, ...)
383 self.sectiontype = sectiontype
385 self.config = map.config
390 self.addremove = false
394 -- Appends a new option
395 function AbstractSection.option(self, class, option, ...)
396 -- Autodetect from UVL
397 if class == true and self.map:get_scheme(self.sectiontype, option) then
398 local vs = self.map:get_scheme(self.sectiontype, option)
399 if vs.type == "boolean" then
401 elseif vs.type == "list" then
403 elseif vs.type == "enum" or vs.type == "reference" then
410 if instanceof(class, AbstractValue) then
411 local obj = class(self.map, self, option, ...)
413 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
417 elseif class == true then
418 error("No valid class was given and autodetection failed.")
420 error("class must be a descendant of AbstractValue")
424 -- Parse optional options
425 function AbstractSection.parse_optionals(self, section)
426 if not self.optional then
430 self.optionals[section] = {}
432 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
433 for k,v in ipairs(self.children) do
434 if v.optional and not v:cfgvalue(section) then
435 if field == v.option then
438 table.insert(self.optionals[section], v)
443 if field and #field > 0 and self.dynamic then
444 self:add_dynamic(field)
448 -- Add a dynamic option
449 function AbstractSection.add_dynamic(self, field, optional)
450 local o = self:option(Value, field, field)
451 o.optional = optional
454 -- Parse all dynamic options
455 function AbstractSection.parse_dynamic(self, section)
456 if not self.dynamic then
460 local arr = luci.util.clone(self:cfgvalue(section))
461 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
462 for k, v in pairs(form) do
466 for key,val in pairs(arr) do
469 for i,c in ipairs(self.children) do
470 if c.option == key then
475 if create and key:sub(1, 1) ~= "." then
476 self:add_dynamic(key, true)
481 -- Returns the section's UCI table
482 function AbstractSection.cfgvalue(self, section)
483 return self.map:get(section)
486 -- Removes the section
487 function AbstractSection.remove(self, section)
488 return self.map:del(section)
491 -- Creates the section
492 function AbstractSection.create(self, section)
496 stat = self.map:set(section, nil, self.sectiontype)
498 section = self.map:add(self.sectiontype)
503 for k,v in pairs(self.children) do
505 self.map:set(section, v.option, v.default)
509 for k,v in pairs(self.defaults) do
510 self.map:set(section, k, v)
518 SimpleSection = class(AbstractSection)
520 function SimpleSection.__init__(self, form, ...)
521 AbstractSection.__init__(self, form, nil, ...)
522 self.template = "cbi/nullsection"
526 Table = class(AbstractSection)
528 function Table.__init__(self, form, data, ...)
529 local datasource = {}
530 datasource.config = "table"
533 function datasource.get(self, section, option)
534 return data[section] and data[section][option]
537 function datasource.del(...)
541 function datasource.get_scheme()
545 AbstractSection.__init__(self, datasource, "table", ...)
546 self.template = "cbi/tblsection"
547 self.rowcolors = true
548 self.anonymous = true
551 function Table.parse(self)
552 for i, k in ipairs(self:cfgsections()) do
553 if luci.http.formvalue("cbi.submit") then
559 function Table.cfgsections(self)
562 for i, v in luci.util.kspairs(self.data) do
563 table.insert(sections, i)
572 NamedSection - A fixed configuration section defined by its name
574 NamedSection = class(AbstractSection)
576 function NamedSection.__init__(self, map, section, stype, ...)
577 AbstractSection.__init__(self, map, stype, ...)
578 Node._i18n(self, map.config, section, nil, ...)
581 self.addremove = false
583 -- Use defaults from UVL
584 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
585 local vs = self.map:get_scheme(self.sectiontype)
586 self.addremove = not vs.unique and not vs.required
587 self.dynamic = vs.dynamic
588 self.title = self.title or vs.title
589 self.description = self.description or vs.descr
592 self.template = "cbi/nsection"
593 self.section = section
596 function NamedSection.parse(self)
597 local s = self.section
598 local active = self:cfgvalue(s)
601 if self.addremove then
602 local path = self.config.."."..s
603 if active then -- Remove the section
604 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
607 else -- Create and apply default values
608 if luci.http.formvalue("cbi.cns."..path) then
616 AbstractSection.parse_dynamic(self, s)
617 if luci.http.formvalue("cbi.submit") then
620 AbstractSection.parse_optionals(self, s)
626 TypedSection - A (set of) configuration section(s) defined by the type
627 addremove: Defines whether the user can add/remove sections of this type
628 anonymous: Allow creating anonymous sections
629 validate: a validation function returning nil if the section is invalid
631 TypedSection = class(AbstractSection)
633 function TypedSection.__init__(self, map, type, ...)
634 AbstractSection.__init__(self, map, type, ...)
635 Node._i18n(self, map.config, type, nil, ...)
637 self.template = "cbi/tsection"
639 self.anonymous = false
641 -- Use defaults from UVL
642 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
643 local vs = self.map:get_scheme(self.sectiontype)
644 self.addremove = not vs.unique and not vs.required
645 self.dynamic = vs.dynamic
646 self.anonymous = not vs.named
647 self.title = self.title or vs.title
648 self.description = self.description or vs.descr
652 -- Return all matching UCI sections for this TypedSection
653 function TypedSection.cfgsections(self)
655 self.map.uci:foreach(self.map.config, self.sectiontype,
657 if self:checkscope(section[".name"]) then
658 table.insert(sections, section[".name"])
665 -- Limits scope to sections that have certain option => value pairs
666 function TypedSection.depends(self, option, value)
667 table.insert(self.deps, {option=option, value=value})
670 function TypedSection.parse(self)
671 if self.addremove then
673 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
674 local name = luci.http.formvalue(crval)
675 if self.anonymous then
681 -- Ignore if it already exists
682 if self:cfgvalue(name) then
686 name = self:checkscope(name)
689 self.err_invalid = true
692 if name and #name > 0 then
699 crval = REMOVE_PREFIX .. self.config
700 name = luci.http.formvaluetable(crval)
701 for k,v in pairs(name) do
702 if self:cfgvalue(k) and self:checkscope(k) then
708 for i, k in ipairs(self:cfgsections()) do
709 AbstractSection.parse_dynamic(self, k)
710 if luci.http.formvalue("cbi.submit") then
713 AbstractSection.parse_optionals(self, k)
717 -- Verifies scope of sections
718 function TypedSection.checkscope(self, section)
719 -- Check if we are not excluded
720 if self.filter and not self:filter(section) then
724 -- Check if at least one dependency is met
725 if #self.deps > 0 and self:cfgvalue(section) then
728 for k, v in ipairs(self.deps) do
729 if self:cfgvalue(section)[v.option] == v.value then
739 return self:validate(section)
743 -- Dummy validate function
744 function TypedSection.validate(self, section)
750 AbstractValue - An abstract Value Type
751 null: Value can be empty
752 valid: A function returning the value if it is valid otherwise nil
753 depends: A table of option => value pairs of which one must be true
754 default: The default value
755 size: The size of the input fields
756 rmempty: Unset value if empty
757 optional: This value is optional (see AbstractSection.optionals)
759 AbstractValue = class(Node)
761 function AbstractValue.__init__(self, map, section, option, ...)
762 Node.__init__(self, ...)
763 self.section = section
766 self.config = map.config
767 self.tag_invalid = {}
768 self.tag_missing = {}
773 self.track_missing = false
777 self.optional = false
779 -- Use defaults from UVL
780 if not self.override_scheme
781 and self.map:get_scheme(self.section.sectiontype, self.option) then
782 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
783 self.rmempty = not vs.required
784 self.cast = (vs.type == "list") and "list" or "string"
785 self.title = self.title or vs.title
786 self.description = self.description or vs.descr
788 if vs.depends and not self.override_dependencies then
789 for i, deps in ipairs(vs.depends) do
790 deps = _uvl_strip_remote_dependencies(deps)
799 -- Add a dependencie to another section field
800 function AbstractValue.depends(self, field, value)
802 if type(field) == "string" then
809 table.insert(self.deps, {deps=deps, add=""})
812 -- Generates the unique CBID
813 function AbstractValue.cbid(self, section)
814 return "cbid."..self.map.config.."."..section.."."..self.option
817 -- Return whether this object should be created
818 function AbstractValue.formcreated(self, section)
819 local key = "cbi.opt."..self.config.."."..section
820 return (luci.http.formvalue(key) == self.option)
823 -- Returns the formvalue for this object
824 function AbstractValue.formvalue(self, section)
825 return luci.http.formvalue(self:cbid(section))
828 function AbstractValue.additional(self, value)
829 self.optional = value
832 function AbstractValue.mandatory(self, value)
833 self.rmempty = not value
836 function AbstractValue.parse(self, section)
837 local fvalue = self:formvalue(section)
838 local cvalue = self:cfgvalue(section)
840 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
841 fvalue = self:transform(self:validate(fvalue, section))
843 self.tag_invalid[section] = true
845 if fvalue and not (fvalue == cvalue) then
846 self:write(section, fvalue)
848 else -- Unset the UCI or error
849 if self.rmempty or self.optional then
851 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
852 self.tag_missing[section] = true
857 -- Render if this value exists or if it is mandatory
858 function AbstractValue.render(self, s, scope)
859 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
862 scope.cbid = self:cbid(s)
863 scope.striptags = luci.util.striptags
865 scope.ifattr = function(cond,key,val)
867 return string.format(
868 ' %s="%s"', tostring(key),
869 luci.util.pcdata(tostring( val
871 or (type(self[key]) ~= "function" and self[key])
879 scope.attr = function(...)
880 return scope.ifattr( true, ... )
883 Node.render(self, scope)
887 -- Return the UCI value of this object
888 function AbstractValue.cfgvalue(self, section)
889 local value = self.map:get(section, self.option)
890 if not self.cast or self.cast == type(value) then
892 elseif self.cast == "string" then
893 if type(value) == "table" then
896 elseif self.cast == "table" then
901 -- Validate the form value
902 function AbstractValue.validate(self, value)
906 AbstractValue.transform = AbstractValue.validate
910 function AbstractValue.write(self, section, value)
911 return self.map:set(section, self.option, value)
915 function AbstractValue.remove(self, section)
916 return self.map:del(section, self.option)
923 Value - A one-line value
924 maxlength: The maximum length
926 Value = class(AbstractValue)
928 function Value.__init__(self, ...)
929 AbstractValue.__init__(self, ...)
930 self.template = "cbi/value"
935 function Value.value(self, key, val)
937 table.insert(self.keylist, tostring(key))
938 table.insert(self.vallist, tostring(val))
942 -- DummyValue - This does nothing except being there
943 DummyValue = class(AbstractValue)
945 function DummyValue.__init__(self, ...)
946 AbstractValue.__init__(self, ...)
947 self.template = "cbi/dvalue"
951 function DummyValue.parse(self)
957 Flag - A flag being enabled or disabled
959 Flag = class(AbstractValue)
961 function Flag.__init__(self, ...)
962 AbstractValue.__init__(self, ...)
963 self.template = "cbi/fvalue"
969 -- A flag can only have two states: set or unset
970 function Flag.parse(self, section)
971 local fvalue = self:formvalue(section)
974 fvalue = self.enabled
976 fvalue = self.disabled
979 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
980 if not(fvalue == self:cfgvalue(section)) then
981 self:write(section, fvalue)
991 ListValue - A one-line value predefined in a list
992 widget: The widget that will be used (select, radio)
994 ListValue = class(AbstractValue)
996 function ListValue.__init__(self, ...)
997 AbstractValue.__init__(self, ...)
998 self.template = "cbi/lvalue"
1003 self.widget = "select"
1005 if not self.override_scheme
1006 and self.map:get_scheme(self.section.sectiontype, self.option) then
1007 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1008 if self.value and vs.values and not self.override_values then
1009 if self.rmempty or self.optional then
1012 for k, v in pairs(vs.values) do
1014 if vs.enum_depends and vs.enum_depends[k] then
1015 for i, dep in ipairs(vs.enum_depends[k]) do
1016 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1019 self:value(k, v, unpack(deps))
1025 function ListValue.value(self, key, val, ...)
1027 table.insert(self.keylist, tostring(key))
1028 table.insert(self.vallist, tostring(val))
1030 for i, deps in ipairs({...}) do
1031 table.insert(self.deps, {add = "-"..key, deps=deps})
1035 function ListValue.validate(self, val)
1036 if luci.util.contains(self.keylist, val) then
1046 MultiValue - Multiple delimited values
1047 widget: The widget that will be used (select, checkbox)
1048 delimiter: The delimiter that will separate the values (default: " ")
1050 MultiValue = class(AbstractValue)
1052 function MultiValue.__init__(self, ...)
1053 AbstractValue.__init__(self, ...)
1054 self.template = "cbi/mvalue"
1059 self.widget = "checkbox"
1060 self.delimiter = " "
1063 function MultiValue.render(self, ...)
1064 if self.widget == "select" and not self.size then
1065 self.size = #self.vallist
1068 AbstractValue.render(self, ...)
1071 function MultiValue.value(self, key, val)
1073 table.insert(self.keylist, tostring(key))
1074 table.insert(self.vallist, tostring(val))
1077 function MultiValue.valuelist(self, section)
1078 local val = self:cfgvalue(section)
1080 if not(type(val) == "string") then
1084 return luci.util.split(val, self.delimiter)
1087 function MultiValue.validate(self, val)
1088 val = (type(val) == "table") and val or {val}
1092 for i, value in ipairs(val) do
1093 if luci.util.contains(self.keylist, value) then
1094 result = result and (result .. self.delimiter .. value) or value
1102 StaticList = class(MultiValue)
1104 function StaticList.__init__(self, ...)
1105 MultiValue.__init__(self, ...)
1107 self.valuelist = self.cfgvalue
1109 if not self.override_scheme
1110 and self.map:get_scheme(self.section.sectiontype, self.option) then
1111 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1112 if self.value and vs.values and not self.override_values then
1113 for k, v in pairs(vs.values) do
1120 function StaticList.validate(self, value)
1121 value = (type(value) == "table") and value or {value}
1124 for i, v in ipairs(value) do
1125 if luci.util.contains(self.valuelist, v) then
1126 table.insert(valid, v)
1133 DynamicList = class(AbstractValue)
1135 function DynamicList.__init__(self, ...)
1136 AbstractValue.__init__(self, ...)
1137 self.template = "cbi/dynlist"
1143 function DynamicList.value(self, key, val)
1145 table.insert(self.keylist, tostring(key))
1146 table.insert(self.vallist, tostring(val))
1149 function DynamicList.validate(self, value, section)
1150 value = (type(value) == "table") and value or {value}
1153 for i, v in ipairs(value) do
1155 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1156 table.insert(valid, v)
1165 TextValue - A multi-line value
1168 TextValue = class(AbstractValue)
1170 function TextValue.__init__(self, ...)
1171 AbstractValue.__init__(self, ...)
1172 self.template = "cbi/tvalue"
1178 Button = class(AbstractValue)
1180 function Button.__init__(self, ...)
1181 AbstractValue.__init__(self, ...)
1182 self.template = "cbi/button"
1183 self.inputstyle = nil