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")
32 require("luci.model.uci")
35 local uci = luci.model.uci
36 local class = luci.util.class
37 local instanceof = luci.util.instanceof
45 CREATE_PREFIX = "cbi.cts."
46 REMOVE_PREFIX = "cbi.rts."
48 -- Loads a CBI map from given file, creating an environment and returns it
49 function load(cbimap, ...)
52 require("luci.config")
55 local cbidir = luci.util.libpath() .. "/model/cbi/"
56 local func, err = loadfile(cbidir..cbimap..".lua")
62 luci.i18n.loadc("cbi")
64 luci.util.resfenv(func)
65 luci.util.updfenv(func, luci.cbi)
66 luci.util.extfenv(func, "translate", luci.i18n.translate)
67 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
68 luci.util.extfenv(func, "arg", {...})
72 for i, map in ipairs(maps) do
73 if not instanceof(map, Node) then
74 error("CBI map returns no valid map object!")
83 function _uvl_strip_remote_dependencies(deps)
86 for k, v in pairs(deps) do
87 k = k:gsub("%$config%.%$section%.", "")
88 if k:match("^[%w_]+$") and type(v) == "string" then
97 -- Node pseudo abstract class
100 function Node.__init__(self, title, description)
102 self.title = title or ""
103 self.description = description or ""
104 self.template = "cbi/node"
108 function Node._i18n(self, config, section, option, title, description)
111 if type(luci.i18n) == "table" then
113 local key = config and config:gsub("[^%w]+", "") or ""
115 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
116 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
118 self.title = title or luci.i18n.translate( key, option or section or config )
119 self.description = description or luci.i18n.translate( key .. "_desc", "" )
123 -- Append child nodes
124 function Node.append(self, obj)
125 table.insert(self.children, obj)
128 -- Parse this node and its children
129 function Node.parse(self, ...)
130 for k, child in ipairs(self.children) do
136 function Node.render(self, scope)
140 luci.template.render(self.template, scope)
143 -- Render the children
144 function Node.render_children(self, ...)
145 for k, node in ipairs(self.children) do
152 A simple template element
154 Template = class(Node)
156 function Template.__init__(self, template)
158 self.template = template
161 function Template.render(self)
162 luci.template.render(self.template, {self=self})
167 Map - A map describing a configuration file
171 function Map.__init__(self, config, ...)
172 Node.__init__(self, ...)
173 Node._i18n(self, config, nil, nil, ...)
176 self.parsechain = {self.config}
177 self.template = "cbi/map"
178 self.uci = uci.cursor()
179 if not self.uci:load(self.config) then
180 error("Unable to read UCI data: " .. self.config)
183 self.validator = luci.uvl.UVL()
184 self.scheme = self.validator:get_scheme(self.config)
187 function Map.get_scheme(self, sectiontype, option)
189 return self.scheme and self.scheme.sections[sectiontype]
191 return self.scheme and self.scheme.variables[sectiontype]
192 and self.scheme.variables[sectiontype][option]
197 -- Chain foreign config
198 function Map.chain(self, config)
199 table.insert(self.parsechain, config)
202 -- Use optimized UCI writing
203 function Map.parse(self, ...)
204 Node.parse(self, ...)
206 for i, config in ipairs(self.parsechain) do
207 self.uci:save(config)
209 if luci.http.formvalue("cbi.apply") then
210 for i, config in ipairs(self.parsechain) do
211 self.uci:commit(config)
212 self.uci:apply(config)
214 -- Refresh data because commit changes section names
215 self.uci:load(config)
219 Node.parse(self, ...)
222 for i, config in ipairs(self.parsechain) do
223 self.uci:unload(config)
227 -- Creates a child section
228 function Map.section(self, class, ...)
229 if instanceof(class, AbstractSection) then
230 local obj = class(self, ...)
234 error("class must be a descendent of AbstractSection")
239 function Map.add(self, sectiontype)
240 return self.uci:add(self.config, sectiontype)
244 function Map.set(self, section, option, value)
246 return self.uci:set(self.config, section, option, value)
248 return self.uci:set(self.config, section, value)
253 function Map.del(self, section, option)
255 return self.uci:delete(self.config, section, option)
257 return self.uci:delete(self.config, section)
262 function Map.get(self, section, option)
264 return self.uci:get_all(self.config)
266 return self.uci:get(self.config, section, option)
268 return self.uci:get_all(self.config, section)
278 Page.__init__ = Node.__init__
279 Page.parse = function() end
283 SimpleForm - A Simple non-UCI form
285 SimpleForm = class(Node)
287 function SimpleForm.__init__(self, config, title, description, data)
288 Node.__init__(self, title, description)
290 self.data = data or {}
291 self.template = "cbi/simpleform"
295 function SimpleForm.parse(self, ...)
296 if luci.http.formvalue("cbi.submit") then
297 Node.parse(self, 1, ...)
301 for k, j in ipairs(self.children) do
302 for i, v in ipairs(j.children) do
304 and (not v.tag_missing or not v.tag_missing[1])
305 and (not v.tag_invalid or not v.tag_invalid[1])
310 not luci.http.formvalue("cbi.submit") and 0
314 self.dorender = not self.handle or self:handle(state, self.data) ~= false
317 function SimpleForm.render(self, ...)
318 if self.dorender then
319 Node.render(self, ...)
323 function SimpleForm.section(self, class, ...)
324 if instanceof(class, AbstractSection) then
325 local obj = class(self, ...)
329 error("class must be a descendent of AbstractSection")
333 -- Creates a child field
334 function SimpleForm.field(self, class, ...)
336 for k, v in ipairs(self.children) do
337 if instanceof(v, SimpleSection) then
343 section = self:section(SimpleSection)
346 if instanceof(class, AbstractValue) then
347 local obj = class(self, ...)
348 obj.track_missing = true
352 error("class must be a descendent of AbstractValue")
356 function SimpleForm.set(self, section, option, value)
357 self.data[option] = value
361 function SimpleForm.del(self, section, option)
362 self.data[option] = nil
366 function SimpleForm.get(self, section, option)
367 return self.data[option]
371 function SimpleForm.get_scheme()
380 AbstractSection = class(Node)
382 function AbstractSection.__init__(self, map, sectiontype, ...)
383 Node.__init__(self, ...)
384 self.sectiontype = sectiontype
386 self.config = map.config
391 self.addremove = false
395 -- Appends a new option
396 function AbstractSection.option(self, class, option, ...)
397 -- Autodetect from UVL
398 if class == true and self.map:get_scheme(self.sectiontype, option) then
399 local vs = self.map:get_scheme(self.sectiontype, option)
400 if vs.type == "boolean" then
402 elseif vs.type == "list" then
404 elseif vs.type == "enum" or vs.type == "reference" then
411 if instanceof(class, AbstractValue) then
412 local obj = class(self.map, self, option, ...)
414 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
418 elseif class == true then
419 error("No valid class was given and autodetection failed.")
421 error("class must be a descendant of AbstractValue")
425 -- Parse optional options
426 function AbstractSection.parse_optionals(self, section)
427 if not self.optional then
431 self.optionals[section] = {}
433 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
434 for k,v in ipairs(self.children) do
435 if v.optional and not v:cfgvalue(section) then
436 if field == v.option then
439 table.insert(self.optionals[section], v)
444 if field and #field > 0 and self.dynamic then
445 self:add_dynamic(field)
449 -- Add a dynamic option
450 function AbstractSection.add_dynamic(self, field, optional)
451 local o = self:option(Value, field, field)
452 o.optional = optional
455 -- Parse all dynamic options
456 function AbstractSection.parse_dynamic(self, section)
457 if not self.dynamic then
461 local arr = luci.util.clone(self:cfgvalue(section))
462 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
463 for k, v in pairs(form) do
467 for key,val in pairs(arr) do
470 for i,c in ipairs(self.children) do
471 if c.option == key then
476 if create and key:sub(1, 1) ~= "." then
477 self:add_dynamic(key, true)
482 -- Returns the section's UCI table
483 function AbstractSection.cfgvalue(self, section)
484 return self.map:get(section)
487 -- Removes the section
488 function AbstractSection.remove(self, section)
489 return self.map:del(section)
492 -- Creates the section
493 function AbstractSection.create(self, section)
497 stat = self.map:set(section, nil, self.sectiontype)
499 section = self.map:add(self.sectiontype)
504 for k,v in pairs(self.children) do
506 self.map:set(section, v.option, v.default)
510 for k,v in pairs(self.defaults) do
511 self.map:set(section, k, v)
519 SimpleSection = class(AbstractSection)
521 function SimpleSection.__init__(self, form, ...)
522 AbstractSection.__init__(self, form, nil, ...)
523 self.template = "cbi/nullsection"
527 Table = class(AbstractSection)
529 function Table.__init__(self, form, data, ...)
530 local datasource = {}
531 datasource.config = "table"
534 function datasource.get(self, section, option)
535 return data[section] and data[section][option]
538 function datasource.del(...)
542 function datasource.get_scheme()
546 AbstractSection.__init__(self, datasource, "table", ...)
547 self.template = "cbi/tblsection"
548 self.rowcolors = true
549 self.anonymous = true
552 function Table.parse(self)
553 for i, k in ipairs(self:cfgsections()) do
554 if luci.http.formvalue("cbi.submit") then
560 function Table.cfgsections(self)
563 for i, v in luci.util.kspairs(self.data) do
564 table.insert(sections, i)
573 NamedSection - A fixed configuration section defined by its name
575 NamedSection = class(AbstractSection)
577 function NamedSection.__init__(self, map, section, stype, ...)
578 AbstractSection.__init__(self, map, stype, ...)
579 Node._i18n(self, map.config, section, nil, ...)
582 self.addremove = false
584 -- Use defaults from UVL
585 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
586 local vs = self.map:get_scheme(self.sectiontype)
587 self.addremove = not vs.unique and not vs.required
588 self.dynamic = vs.dynamic
589 self.title = self.title or vs.title
590 self.description = self.description or vs.descr
593 self.template = "cbi/nsection"
594 self.section = section
597 function NamedSection.parse(self)
598 local s = self.section
599 local active = self:cfgvalue(s)
602 if self.addremove then
603 local path = self.config.."."..s
604 if active then -- Remove the section
605 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
608 else -- Create and apply default values
609 if luci.http.formvalue("cbi.cns."..path) then
617 AbstractSection.parse_dynamic(self, s)
618 if luci.http.formvalue("cbi.submit") then
621 AbstractSection.parse_optionals(self, s)
627 TypedSection - A (set of) configuration section(s) defined by the type
628 addremove: Defines whether the user can add/remove sections of this type
629 anonymous: Allow creating anonymous sections
630 validate: a validation function returning nil if the section is invalid
632 TypedSection = class(AbstractSection)
634 function TypedSection.__init__(self, map, type, ...)
635 AbstractSection.__init__(self, map, type, ...)
636 Node._i18n(self, map.config, type, nil, ...)
638 self.template = "cbi/tsection"
640 self.anonymous = false
642 -- Use defaults from UVL
643 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
644 local vs = self.map:get_scheme(self.sectiontype)
645 self.addremove = not vs.unique and not vs.required
646 self.dynamic = vs.dynamic
647 self.anonymous = not vs.named
648 self.title = self.title or vs.title
649 self.description = self.description or vs.descr
653 -- Return all matching UCI sections for this TypedSection
654 function TypedSection.cfgsections(self)
656 self.map.uci:foreach(self.map.config, self.sectiontype,
658 if self:checkscope(section[".name"]) then
659 table.insert(sections, section[".name"])
666 -- Limits scope to sections that have certain option => value pairs
667 function TypedSection.depends(self, option, value)
668 table.insert(self.deps, {option=option, value=value})
671 function TypedSection.parse(self)
672 if self.addremove then
674 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
675 local name = luci.http.formvalue(crval)
676 if self.anonymous then
682 -- Ignore if it already exists
683 if self:cfgvalue(name) then
687 name = self:checkscope(name)
690 self.err_invalid = true
693 if name and #name > 0 then
700 crval = REMOVE_PREFIX .. self.config
701 name = luci.http.formvaluetable(crval)
702 for k,v in pairs(name) do
703 if self:cfgvalue(k) and self:checkscope(k) then
709 for i, k in ipairs(self:cfgsections()) do
710 AbstractSection.parse_dynamic(self, k)
711 if luci.http.formvalue("cbi.submit") then
714 AbstractSection.parse_optionals(self, k)
718 -- Verifies scope of sections
719 function TypedSection.checkscope(self, section)
720 -- Check if we are not excluded
721 if self.filter and not self:filter(section) then
725 -- Check if at least one dependency is met
726 if #self.deps > 0 and self:cfgvalue(section) then
729 for k, v in ipairs(self.deps) do
730 if self:cfgvalue(section)[v.option] == v.value then
740 return self:validate(section)
744 -- Dummy validate function
745 function TypedSection.validate(self, section)
751 AbstractValue - An abstract Value Type
752 null: Value can be empty
753 valid: A function returning the value if it is valid otherwise nil
754 depends: A table of option => value pairs of which one must be true
755 default: The default value
756 size: The size of the input fields
757 rmempty: Unset value if empty
758 optional: This value is optional (see AbstractSection.optionals)
760 AbstractValue = class(Node)
762 function AbstractValue.__init__(self, map, section, option, ...)
763 Node.__init__(self, ...)
764 self.section = section
767 self.config = map.config
768 self.tag_invalid = {}
769 self.tag_missing = {}
774 self.track_missing = false
778 self.optional = false
780 -- Use defaults from UVL
781 if not self.override_scheme
782 and self.map:get_scheme(self.section.sectiontype, self.option) then
783 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
784 self.rmempty = not vs.required
785 self.cast = (vs.type == "list") and "list" or "string"
786 self.title = self.title or vs.title
787 self.description = self.description or vs.descr
789 if vs.depends and not self.override_dependencies then
790 for i, deps in ipairs(vs.depends) do
791 deps = _uvl_strip_remote_dependencies(deps)
800 -- Add a dependencie to another section field
801 function AbstractValue.depends(self, field, value)
803 if type(field) == "string" then
810 table.insert(self.deps, {deps=deps, add=""})
813 -- Generates the unique CBID
814 function AbstractValue.cbid(self, section)
815 return "cbid."..self.map.config.."."..section.."."..self.option
818 -- Return whether this object should be created
819 function AbstractValue.formcreated(self, section)
820 local key = "cbi.opt."..self.config.."."..section
821 return (luci.http.formvalue(key) == self.option)
824 -- Returns the formvalue for this object
825 function AbstractValue.formvalue(self, section)
826 return luci.http.formvalue(self:cbid(section))
829 function AbstractValue.additional(self, value)
830 self.optional = value
833 function AbstractValue.mandatory(self, value)
834 self.rmempty = not value
837 function AbstractValue.parse(self, section)
838 local fvalue = self:formvalue(section)
839 local cvalue = self:cfgvalue(section)
841 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
842 fvalue = self:transform(self:validate(fvalue, section))
844 self.tag_invalid[section] = true
846 if fvalue and not (fvalue == cvalue) then
847 self:write(section, fvalue)
849 else -- Unset the UCI or error
850 if self.rmempty or self.optional then
852 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
853 self.tag_missing[section] = true
858 -- Render if this value exists or if it is mandatory
859 function AbstractValue.render(self, s, scope)
860 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
863 scope.cbid = self:cbid(s)
864 scope.striptags = luci.util.striptags
866 scope.ifattr = function(cond,key,val)
868 return string.format(
869 ' %s="%s"', tostring(key),
870 luci.util.pcdata(tostring( val
872 or (type(self[key]) ~= "function" and self[key])
880 scope.attr = function(...)
881 return scope.ifattr( true, ... )
884 Node.render(self, scope)
888 -- Return the UCI value of this object
889 function AbstractValue.cfgvalue(self, section)
890 local value = self.map:get(section, self.option)
891 if not self.cast or self.cast == type(value) then
893 elseif self.cast == "string" then
894 if type(value) == "table" then
897 elseif self.cast == "table" then
902 -- Validate the form value
903 function AbstractValue.validate(self, value)
907 AbstractValue.transform = AbstractValue.validate
911 function AbstractValue.write(self, section, value)
912 return self.map:set(section, self.option, value)
916 function AbstractValue.remove(self, section)
917 return self.map:del(section, self.option)
924 Value - A one-line value
925 maxlength: The maximum length
927 Value = class(AbstractValue)
929 function Value.__init__(self, ...)
930 AbstractValue.__init__(self, ...)
931 self.template = "cbi/value"
936 function Value.value(self, key, val)
938 table.insert(self.keylist, tostring(key))
939 table.insert(self.vallist, tostring(val))
943 -- DummyValue - This does nothing except being there
944 DummyValue = class(AbstractValue)
946 function DummyValue.__init__(self, ...)
947 AbstractValue.__init__(self, ...)
948 self.template = "cbi/dvalue"
952 function DummyValue.parse(self)
958 Flag - A flag being enabled or disabled
960 Flag = class(AbstractValue)
962 function Flag.__init__(self, ...)
963 AbstractValue.__init__(self, ...)
964 self.template = "cbi/fvalue"
970 -- A flag can only have two states: set or unset
971 function Flag.parse(self, section)
972 local fvalue = self:formvalue(section)
975 fvalue = self.enabled
977 fvalue = self.disabled
980 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
981 if not(fvalue == self:cfgvalue(section)) then
982 self:write(section, fvalue)
992 ListValue - A one-line value predefined in a list
993 widget: The widget that will be used (select, radio)
995 ListValue = class(AbstractValue)
997 function ListValue.__init__(self, ...)
998 AbstractValue.__init__(self, ...)
999 self.template = "cbi/lvalue"
1004 self.widget = "select"
1006 if not self.override_scheme
1007 and self.map:get_scheme(self.section.sectiontype, self.option) then
1008 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1009 if self.value and vs.values and not self.override_values then
1010 if self.rmempty or self.optional then
1013 for k, v in pairs(vs.values) do
1015 if vs.enum_depends and vs.enum_depends[k] then
1016 deps = _uvl_strip_remote_dependencies(vs.enum_depends[k])
1018 self:value(k, v, unpack(deps))
1024 function ListValue.value(self, key, val, ...)
1026 table.insert(self.keylist, tostring(key))
1027 table.insert(self.vallist, tostring(val))
1029 for i, deps in ipairs({...}) do
1030 table.insert(self.deps, {add = "-"..key, deps=deps})
1034 function ListValue.validate(self, val)
1035 if luci.util.contains(self.keylist, val) then
1045 MultiValue - Multiple delimited values
1046 widget: The widget that will be used (select, checkbox)
1047 delimiter: The delimiter that will separate the values (default: " ")
1049 MultiValue = class(AbstractValue)
1051 function MultiValue.__init__(self, ...)
1052 AbstractValue.__init__(self, ...)
1053 self.template = "cbi/mvalue"
1058 self.widget = "checkbox"
1059 self.delimiter = " "
1062 function MultiValue.render(self, ...)
1063 if self.widget == "select" and not self.size then
1064 self.size = #self.vallist
1067 AbstractValue.render(self, ...)
1070 function MultiValue.value(self, key, val)
1072 table.insert(self.keylist, tostring(key))
1073 table.insert(self.vallist, tostring(val))
1076 function MultiValue.valuelist(self, section)
1077 local val = self:cfgvalue(section)
1079 if not(type(val) == "string") then
1083 return luci.util.split(val, self.delimiter)
1086 function MultiValue.validate(self, val)
1087 val = (type(val) == "table") and val or {val}
1091 for i, value in ipairs(val) do
1092 if luci.util.contains(self.keylist, value) then
1093 result = result and (result .. self.delimiter .. value) or value
1101 StaticList = class(MultiValue)
1103 function StaticList.__init__(self, ...)
1104 MultiValue.__init__(self, ...)
1106 self.valuelist = self.cfgvalue
1108 if not self.override_scheme
1109 and self.map:get_scheme(self.section.sectiontype, self.option) then
1110 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1111 if self.value and vs.values and not self.override_values then
1112 for k, v in pairs(vs.values) do
1119 function StaticList.validate(self, value)
1120 value = (type(value) == "table") and value or {value}
1123 for i, v in ipairs(value) do
1124 if luci.util.contains(self.valuelist, v) then
1125 table.insert(valid, v)
1132 DynamicList = class(AbstractValue)
1134 function DynamicList.__init__(self, ...)
1135 AbstractValue.__init__(self, ...)
1136 self.template = "cbi/dynlist"
1142 function DynamicList.value(self, key, val)
1144 table.insert(self.keylist, tostring(key))
1145 table.insert(self.vallist, tostring(val))
1148 function DynamicList.validate(self, value, section)
1149 value = (type(value) == "table") and value or {value}
1152 for i, v in ipairs(value) do
1154 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1155 table.insert(valid, v)
1164 TextValue - A multi-line value
1167 TextValue = class(AbstractValue)
1169 function TextValue.__init__(self, ...)
1170 AbstractValue.__init__(self, ...)
1171 self.template = "cbi/tvalue"
1177 Button = class(AbstractValue)
1179 function Button.__init__(self, ...)
1180 AbstractValue.__init__(self, ...)
1181 self.template = "cbi/button"
1182 self.inputstyle = nil