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 if not self.override_scheme and self.map.scheme then
621 local co = self.map:get()
622 local stat, err = self.map.validator:validate_section(self.config, s, co)
623 luci.http.prepare_content("text/html")
624 luci.util.dumptable(err)
627 AbstractSection.parse_optionals(self, s)
633 TypedSection - A (set of) configuration section(s) defined by the type
634 addremove: Defines whether the user can add/remove sections of this type
635 anonymous: Allow creating anonymous sections
636 validate: a validation function returning nil if the section is invalid
638 TypedSection = class(AbstractSection)
640 function TypedSection.__init__(self, map, type, ...)
641 AbstractSection.__init__(self, map, type, ...)
642 Node._i18n(self, map.config, type, nil, ...)
644 self.template = "cbi/tsection"
646 self.anonymous = false
648 -- Use defaults from UVL
649 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
650 local vs = self.map:get_scheme(self.sectiontype)
651 self.addremove = not vs.unique and not vs.required
652 self.dynamic = vs.dynamic
653 self.anonymous = not vs.named
654 self.title = self.title or vs.title
655 self.description = self.description or vs.descr
659 -- Return all matching UCI sections for this TypedSection
660 function TypedSection.cfgsections(self)
662 self.map.uci:foreach(self.map.config, self.sectiontype,
664 if self:checkscope(section[".name"]) then
665 table.insert(sections, section[".name"])
672 -- Limits scope to sections that have certain option => value pairs
673 function TypedSection.depends(self, option, value)
674 table.insert(self.deps, {option=option, value=value})
677 function TypedSection.parse(self)
678 if self.addremove then
680 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
681 local name = luci.http.formvalue(crval)
682 if self.anonymous then
688 -- Ignore if it already exists
689 if self:cfgvalue(name) then
693 name = self:checkscope(name)
696 self.err_invalid = true
699 if name and #name > 0 then
706 crval = REMOVE_PREFIX .. self.config
707 name = luci.http.formvaluetable(crval)
708 for k,v in pairs(name) do
709 if self:cfgvalue(k) and self:checkscope(k) then
716 for i, k in ipairs(self:cfgsections()) do
717 AbstractSection.parse_dynamic(self, k)
718 if luci.http.formvalue("cbi.submit") then
721 if not self.override_scheme and self.map.scheme then
722 co = co or self.map:get()
723 local stat, err = self.map.uvl:validate_section(self.config, k, co)
724 luci.util.perror(err)
727 AbstractSection.parse_optionals(self, k)
731 -- Verifies scope of sections
732 function TypedSection.checkscope(self, section)
733 -- Check if we are not excluded
734 if self.filter and not self:filter(section) then
738 -- Check if at least one dependency is met
739 if #self.deps > 0 and self:cfgvalue(section) then
742 for k, v in ipairs(self.deps) do
743 if self:cfgvalue(section)[v.option] == v.value then
753 return self:validate(section)
757 -- Dummy validate function
758 function TypedSection.validate(self, section)
764 AbstractValue - An abstract Value Type
765 null: Value can be empty
766 valid: A function returning the value if it is valid otherwise nil
767 depends: A table of option => value pairs of which one must be true
768 default: The default value
769 size: The size of the input fields
770 rmempty: Unset value if empty
771 optional: This value is optional (see AbstractSection.optionals)
773 AbstractValue = class(Node)
775 function AbstractValue.__init__(self, map, section, option, ...)
776 Node.__init__(self, ...)
777 self.section = section
780 self.config = map.config
781 self.tag_invalid = {}
782 self.tag_missing = {}
787 self.track_missing = false
791 self.optional = false
793 -- Use defaults from UVL
794 if not self.override_scheme
795 and self.map:get_scheme(self.section.sectiontype, self.option) then
796 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
797 self.rmempty = not vs.required
798 self.cast = (vs.type == "list") and "list" or "string"
799 self.title = self.title or vs.title
800 self.description = self.description or vs.descr
802 if vs.depends and not self.override_dependencies then
803 for i, deps in ipairs(vs.depends) do
804 deps = _uvl_strip_remote_dependencies(deps)
813 -- Add a dependencie to another section field
814 function AbstractValue.depends(self, field, value)
816 if type(field) == "string" then
823 table.insert(self.deps, {deps=deps, add=""})
826 -- Generates the unique CBID
827 function AbstractValue.cbid(self, section)
828 return "cbid."..self.map.config.."."..section.."."..self.option
831 -- Return whether this object should be created
832 function AbstractValue.formcreated(self, section)
833 local key = "cbi.opt."..self.config.."."..section
834 return (luci.http.formvalue(key) == self.option)
837 -- Returns the formvalue for this object
838 function AbstractValue.formvalue(self, section)
839 return luci.http.formvalue(self:cbid(section))
842 function AbstractValue.additional(self, value)
843 self.optional = value
846 function AbstractValue.mandatory(self, value)
847 self.rmempty = not value
850 function AbstractValue.parse(self, section)
851 local fvalue = self:formvalue(section)
852 local cvalue = self:cfgvalue(section)
854 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
855 fvalue = self:transform(self:validate(fvalue, section))
857 self.tag_invalid[section] = true
859 if fvalue and not (fvalue == cvalue) then
860 self:write(section, fvalue)
862 else -- Unset the UCI or error
863 if self.rmempty or self.optional then
865 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
866 self.tag_missing[section] = true
871 -- Render if this value exists or if it is mandatory
872 function AbstractValue.render(self, s, scope)
873 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
876 scope.cbid = self:cbid(s)
877 scope.striptags = luci.util.striptags
879 scope.ifattr = function(cond,key,val)
881 return string.format(
882 ' %s="%s"', tostring(key),
883 luci.util.pcdata(tostring( val
885 or (type(self[key]) ~= "function" and self[key])
893 scope.attr = function(...)
894 return scope.ifattr( true, ... )
897 Node.render(self, scope)
901 -- Return the UCI value of this object
902 function AbstractValue.cfgvalue(self, section)
903 local value = self.map:get(section, self.option)
904 if not self.cast or self.cast == type(value) then
906 elseif self.cast == "string" then
907 if type(value) == "table" then
910 elseif self.cast == "table" then
915 -- Validate the form value
916 function AbstractValue.validate(self, value)
920 AbstractValue.transform = AbstractValue.validate
924 function AbstractValue.write(self, section, value)
925 return self.map:set(section, self.option, value)
929 function AbstractValue.remove(self, section)
930 return self.map:del(section, self.option)
937 Value - A one-line value
938 maxlength: The maximum length
940 Value = class(AbstractValue)
942 function Value.__init__(self, ...)
943 AbstractValue.__init__(self, ...)
944 self.template = "cbi/value"
949 function Value.value(self, key, val)
951 table.insert(self.keylist, tostring(key))
952 table.insert(self.vallist, tostring(val))
956 -- DummyValue - This does nothing except being there
957 DummyValue = class(AbstractValue)
959 function DummyValue.__init__(self, ...)
960 AbstractValue.__init__(self, ...)
961 self.template = "cbi/dvalue"
965 function DummyValue.parse(self)
971 Flag - A flag being enabled or disabled
973 Flag = class(AbstractValue)
975 function Flag.__init__(self, ...)
976 AbstractValue.__init__(self, ...)
977 self.template = "cbi/fvalue"
983 -- A flag can only have two states: set or unset
984 function Flag.parse(self, section)
985 local fvalue = self:formvalue(section)
988 fvalue = self.enabled
990 fvalue = self.disabled
993 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
994 if not(fvalue == self:cfgvalue(section)) then
995 self:write(section, fvalue)
1005 ListValue - A one-line value predefined in a list
1006 widget: The widget that will be used (select, radio)
1008 ListValue = class(AbstractValue)
1010 function ListValue.__init__(self, ...)
1011 AbstractValue.__init__(self, ...)
1012 self.template = "cbi/lvalue"
1017 self.widget = "select"
1019 if not self.override_scheme
1020 and self.map:get_scheme(self.section.sectiontype, self.option) then
1021 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1022 if self.value and vs.values and not self.override_values then
1023 if self.rmempty or self.optional then
1026 for k, v in pairs(vs.values) do
1028 if vs.enum_depends and vs.enum_depends[k] then
1029 for i, dep in ipairs(vs.enum_depends[k]) do
1030 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1033 self:value(k, v, unpack(deps))
1039 function ListValue.value(self, key, val, ...)
1041 table.insert(self.keylist, tostring(key))
1042 table.insert(self.vallist, tostring(val))
1044 for i, deps in ipairs({...}) do
1045 table.insert(self.deps, {add = "-"..key, deps=deps})
1049 function ListValue.validate(self, val)
1050 if luci.util.contains(self.keylist, val) then
1060 MultiValue - Multiple delimited values
1061 widget: The widget that will be used (select, checkbox)
1062 delimiter: The delimiter that will separate the values (default: " ")
1064 MultiValue = class(AbstractValue)
1066 function MultiValue.__init__(self, ...)
1067 AbstractValue.__init__(self, ...)
1068 self.template = "cbi/mvalue"
1073 self.widget = "checkbox"
1074 self.delimiter = " "
1077 function MultiValue.render(self, ...)
1078 if self.widget == "select" and not self.size then
1079 self.size = #self.vallist
1082 AbstractValue.render(self, ...)
1085 function MultiValue.value(self, key, val)
1087 table.insert(self.keylist, tostring(key))
1088 table.insert(self.vallist, tostring(val))
1091 function MultiValue.valuelist(self, section)
1092 local val = self:cfgvalue(section)
1094 if not(type(val) == "string") then
1098 return luci.util.split(val, self.delimiter)
1101 function MultiValue.validate(self, val)
1102 val = (type(val) == "table") and val or {val}
1106 for i, value in ipairs(val) do
1107 if luci.util.contains(self.keylist, value) then
1108 result = result and (result .. self.delimiter .. value) or value
1116 StaticList = class(MultiValue)
1118 function StaticList.__init__(self, ...)
1119 MultiValue.__init__(self, ...)
1121 self.valuelist = self.cfgvalue
1123 if not self.override_scheme
1124 and self.map:get_scheme(self.section.sectiontype, self.option) then
1125 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1126 if self.value and vs.values and not self.override_values then
1127 for k, v in pairs(vs.values) do
1134 function StaticList.validate(self, value)
1135 value = (type(value) == "table") and value or {value}
1138 for i, v in ipairs(value) do
1139 if luci.util.contains(self.valuelist, v) then
1140 table.insert(valid, v)
1147 DynamicList = class(AbstractValue)
1149 function DynamicList.__init__(self, ...)
1150 AbstractValue.__init__(self, ...)
1151 self.template = "cbi/dynlist"
1157 function DynamicList.value(self, key, val)
1159 table.insert(self.keylist, tostring(key))
1160 table.insert(self.vallist, tostring(val))
1163 function DynamicList.validate(self, value, section)
1164 value = (type(value) == "table") and value or {value}
1167 for i, v in ipairs(value) do
1169 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1170 table.insert(valid, v)
1179 TextValue - A multi-line value
1182 TextValue = class(AbstractValue)
1184 function TextValue.__init__(self, ...)
1185 AbstractValue.__init__(self, ...)
1186 self.template = "cbi/tvalue"
1192 Button = class(AbstractValue)
1194 function Button.__init__(self, ...)
1195 AbstractValue.__init__(self, ...)
1196 self.template = "cbi/button"
1197 self.inputstyle = nil