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 if not uci.load_config(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]
195 function Map.render(self, ...)
196 if self.stateful then
197 uci.load_state(self.config)
199 uci.load_config(self.config)
201 Node.render(self, ...)
205 -- Chain foreign config
206 function Map.chain(self, config)
207 table.insert(self.parsechain, config)
210 -- Use optimized UCI writing
211 function Map.parse(self, ...)
212 if self.stateful then
213 uci.load_state(self.config)
215 uci.load_config(self.config)
218 Node.parse(self, ...)
220 for i, config in ipairs(self.parsechain) do
221 uci.save_config(config)
223 if luci.http.formvalue("cbi.apply") then
224 for i, config in ipairs(self.parsechain) do
226 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
227 luci.util.exec(luci.config.uci_oncommit[config])
230 -- Refresh data because commit changes section names
231 uci.load_config(config)
235 Node.parse(self, ...)
238 for i, config in ipairs(self.parsechain) do
243 -- Creates a child section
244 function Map.section(self, class, ...)
245 if instanceof(class, AbstractSection) then
246 local obj = class(self, ...)
250 error("class must be a descendent of AbstractSection")
255 function Map.add(self, sectiontype)
256 return uci.add(self.config, sectiontype)
260 function Map.set(self, section, option, value)
262 return uci.set(self.config, section, option, value)
264 return uci.set(self.config, section, value)
269 function Map.del(self, section, option)
271 return uci.delete(self.config, section, option)
273 return uci.delete(self.config, section)
278 function Map.get(self, section, option)
280 return uci.get_all(self.config)
282 return uci.get(self.config, section, option)
284 return uci.get_all(self.config, section)
294 Page.__init__ = Node.__init__
295 Page.parse = function() end
299 SimpleForm - A Simple non-UCI form
301 SimpleForm = class(Node)
303 function SimpleForm.__init__(self, config, title, description, data)
304 Node.__init__(self, title, description)
306 self.data = data or {}
307 self.template = "cbi/simpleform"
311 function SimpleForm.parse(self, ...)
312 if luci.http.formvalue("cbi.submit") then
313 Node.parse(self, 1, ...)
317 for k, j in ipairs(self.children) do
318 for i, v in ipairs(j.children) do
320 and (not v.tag_missing or not v.tag_missing[1])
321 and (not v.tag_invalid or not v.tag_invalid[1])
326 not luci.http.formvalue("cbi.submit") and 0
330 self.dorender = not self.handle or self:handle(state, self.data) ~= false
333 function SimpleForm.render(self, ...)
334 if self.dorender then
335 Node.render(self, ...)
339 function SimpleForm.section(self, class, ...)
340 if instanceof(class, AbstractSection) then
341 local obj = class(self, ...)
345 error("class must be a descendent of AbstractSection")
349 -- Creates a child field
350 function SimpleForm.field(self, class, ...)
352 for k, v in ipairs(self.children) do
353 if instanceof(v, SimpleSection) then
359 section = self:section(SimpleSection)
362 if instanceof(class, AbstractValue) then
363 local obj = class(self, ...)
364 obj.track_missing = true
368 error("class must be a descendent of AbstractValue")
372 function SimpleForm.set(self, section, option, value)
373 self.data[option] = value
377 function SimpleForm.del(self, section, option)
378 self.data[option] = nil
382 function SimpleForm.get(self, section, option)
383 return self.data[option]
391 AbstractSection = class(Node)
393 function AbstractSection.__init__(self, map, sectiontype, ...)
394 Node.__init__(self, ...)
395 self.sectiontype = sectiontype
397 self.config = map.config
402 self.addremove = false
406 -- Appends a new option
407 function AbstractSection.option(self, class, option, ...)
408 -- Autodetect from UVL
409 if class == true and self.map:get_scheme(self.sectiontype, option) then
410 local vs = self.map:get_scheme(self.sectiontype, option)
411 if vs.type == "boolean" then
413 elseif vs.type == "list" then
415 elseif vs.type == "enum" or vs.type == "reference" then
422 if instanceof(class, AbstractValue) then
423 local obj = class(self.map, self, option, ...)
425 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
429 elseif class == true then
430 error("No valid class was given and autodetection failed.")
432 error("class must be a descendant of AbstractValue")
436 -- Parse optional options
437 function AbstractSection.parse_optionals(self, section)
438 if not self.optional then
442 self.optionals[section] = {}
444 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
445 for k,v in ipairs(self.children) do
446 if v.optional and not v:cfgvalue(section) then
447 if field == v.option then
450 table.insert(self.optionals[section], v)
455 if field and #field > 0 and self.dynamic then
456 self:add_dynamic(field)
460 -- Add a dynamic option
461 function AbstractSection.add_dynamic(self, field, optional)
462 local o = self:option(Value, field, field)
463 o.optional = optional
466 -- Parse all dynamic options
467 function AbstractSection.parse_dynamic(self, section)
468 if not self.dynamic then
472 local arr = luci.util.clone(self:cfgvalue(section))
473 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
474 for k, v in pairs(form) do
478 for key,val in pairs(arr) do
481 for i,c in ipairs(self.children) do
482 if c.option == key then
487 if create and key:sub(1, 1) ~= "." then
488 self:add_dynamic(key, true)
493 -- Returns the section's UCI table
494 function AbstractSection.cfgvalue(self, section)
495 return self.map:get(section)
498 -- Removes the section
499 function AbstractSection.remove(self, section)
500 return self.map:del(section)
503 -- Creates the section
504 function AbstractSection.create(self, section)
508 stat = self.map:set(section, nil, self.sectiontype)
510 section = self.map:add(self.sectiontype)
515 for k,v in pairs(self.children) do
517 self.map:set(section, v.option, v.default)
521 for k,v in pairs(self.defaults) do
522 self.map:set(section, k, v)
530 SimpleSection = class(AbstractSection)
532 function SimpleSection.__init__(self, form, ...)
533 AbstractSection.__init__(self, form, nil, ...)
534 self.template = "cbi/nullsection"
538 Table = class(AbstractSection)
540 function Table.__init__(self, form, data, ...)
541 local datasource = {}
542 datasource.config = "table"
545 function datasource.get(self, section, option)
546 return data[section] and data[section][option]
549 function datasource.del(...)
553 AbstractSection.__init__(self, datasource, "table", ...)
554 self.template = "cbi/tblsection"
555 self.rowcolors = true
556 self.anonymous = true
559 function Table.parse(self)
560 for i, k in ipairs(self:cfgsections()) do
561 if luci.http.formvalue("cbi.submit") then
567 function Table.cfgsections(self)
570 for i, v in luci.util.kspairs(self.data) do
571 table.insert(sections, i)
580 NamedSection - A fixed configuration section defined by its name
582 NamedSection = class(AbstractSection)
584 function NamedSection.__init__(self, map, section, stype, ...)
585 AbstractSection.__init__(self, map, stype, ...)
586 Node._i18n(self, map.config, section, nil, ...)
589 self.addremove = false
591 -- Use defaults from UVL
592 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
593 local vs = self.map:get_scheme(self.sectiontype)
594 self.addremove = not vs.unique and not vs.required
595 self.dynamic = vs.dynamic
596 self.title = self.title or vs.title
597 self.description = self.description or vs.descr
600 self.template = "cbi/nsection"
601 self.section = section
604 function NamedSection.parse(self)
605 local s = self.section
606 local active = self:cfgvalue(s)
609 if self.addremove then
610 local path = self.config.."."..s
611 if active then -- Remove the section
612 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
615 else -- Create and apply default values
616 if luci.http.formvalue("cbi.cns."..path) then
624 AbstractSection.parse_dynamic(self, s)
625 if luci.http.formvalue("cbi.submit") then
628 AbstractSection.parse_optionals(self, s)
634 TypedSection - A (set of) configuration section(s) defined by the type
635 addremove: Defines whether the user can add/remove sections of this type
636 anonymous: Allow creating anonymous sections
637 validate: a validation function returning nil if the section is invalid
639 TypedSection = class(AbstractSection)
641 function TypedSection.__init__(self, map, type, ...)
642 AbstractSection.__init__(self, map, type, ...)
643 Node._i18n(self, map.config, type, nil, ...)
645 self.template = "cbi/tsection"
647 self.anonymous = false
649 -- Use defaults from UVL
650 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
651 local vs = self.map:get_scheme(self.sectiontype)
652 self.addremove = not vs.unique and not vs.required
653 self.dynamic = vs.dynamic
654 self.anonymous = not vs.named
655 self.title = self.title or vs.title
656 self.description = self.description or vs.descr
660 -- Return all matching UCI sections for this TypedSection
661 function TypedSection.cfgsections(self)
663 uci.foreach(self.map.config, self.sectiontype,
665 if self:checkscope(section[".name"]) then
666 table.insert(sections, section[".name"])
673 -- Limits scope to sections that have certain option => value pairs
674 function TypedSection.depends(self, option, value)
675 table.insert(self.deps, {option=option, value=value})
678 function TypedSection.parse(self)
679 if self.addremove then
681 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
682 local name = luci.http.formvalue(crval)
683 if self.anonymous then
689 -- Ignore if it already exists
690 if self:cfgvalue(name) then
694 name = self:checkscope(name)
697 self.err_invalid = true
700 if name and name:len() > 0 then
707 crval = REMOVE_PREFIX .. self.config
708 name = luci.http.formvaluetable(crval)
709 for k,v in pairs(name) do
711 luci.util.perror(self:cfgvalue(k))
712 luci.util.perror(self:checkscope(k))
713 if self:cfgvalue(k) and self:checkscope(k) then
719 for i, k in ipairs(self:cfgsections()) do
720 AbstractSection.parse_dynamic(self, k)
721 if luci.http.formvalue("cbi.submit") then
724 AbstractSection.parse_optionals(self, k)
728 -- Verifies scope of sections
729 function TypedSection.checkscope(self, section)
730 -- Check if we are not excluded
731 if self.filter and not self:filter(section) then
735 -- Check if at least one dependency is met
736 if #self.deps > 0 and self:cfgvalue(section) then
739 for k, v in ipairs(self.deps) do
740 if self:cfgvalue(section)[v.option] == v.value then
750 return self:validate(section)
754 -- Dummy validate function
755 function TypedSection.validate(self, section)
761 AbstractValue - An abstract Value Type
762 null: Value can be empty
763 valid: A function returning the value if it is valid otherwise nil
764 depends: A table of option => value pairs of which one must be true
765 default: The default value
766 size: The size of the input fields
767 rmempty: Unset value if empty
768 optional: This value is optional (see AbstractSection.optionals)
770 AbstractValue = class(Node)
772 function AbstractValue.__init__(self, map, section, option, ...)
773 Node.__init__(self, ...)
774 self.section = section
777 self.config = map.config
778 self.tag_invalid = {}
779 self.tag_missing = {}
784 self.track_missing = false
788 self.optional = false
790 -- Use defaults from UVL
791 if not self.override_scheme
792 and self.map:get_scheme(self.section.sectiontype, self.option) then
793 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
794 self.rmempty = not vs.required
795 self.cast = (vs.type == "list") and "list" or "string"
796 self.title = self.title or vs.title
797 self.description = self.description or vs.descr
799 if vs.depends and not self.override_dependencies then
800 for i, deps in ipairs(vs.depends) do
801 deps = _uvl_strip_remote_dependencies(deps)
810 -- Add a dependencie to another section field
811 function AbstractValue.depends(self, field, value)
813 if type(field) == "string" then
820 table.insert(self.deps, {deps=deps, add=""})
823 -- Generates the unique CBID
824 function AbstractValue.cbid(self, section)
825 return "cbid."..self.map.config.."."..section.."."..self.option
828 -- Return whether this object should be created
829 function AbstractValue.formcreated(self, section)
830 local key = "cbi.opt."..self.config.."."..section
831 return (luci.http.formvalue(key) == self.option)
834 -- Returns the formvalue for this object
835 function AbstractValue.formvalue(self, section)
836 return luci.http.formvalue(self:cbid(section))
839 function AbstractValue.additional(self, value)
840 self.optional = value
843 function AbstractValue.mandatory(self, value)
844 self.rmempty = not value
847 function AbstractValue.parse(self, section)
848 local fvalue = self:formvalue(section)
849 local cvalue = self:cfgvalue(section)
851 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
852 fvalue = self:transform(self:validate(fvalue, section))
854 self.tag_invalid[section] = true
856 if fvalue and not (fvalue == cvalue) then
857 self:write(section, fvalue)
859 else -- Unset the UCI or error
860 if self.rmempty or self.optional then
862 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
863 self.tag_missing[section] = true
868 -- Render if this value exists or if it is mandatory
869 function AbstractValue.render(self, s, scope)
870 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
873 scope.cbid = self:cbid(s)
874 scope.striptags = luci.util.striptags
876 scope.ifattr = function(cond,key,val)
878 return string.format(
879 ' %s="%s"', tostring(key),
880 luci.util.pcdata(tostring( val
882 or (type(self[key]) ~= "function" and self[key])
890 scope.attr = function(...)
891 return scope.ifattr( true, ... )
894 Node.render(self, scope)
898 -- Return the UCI value of this object
899 function AbstractValue.cfgvalue(self, section)
900 local value = self.map:get(section, self.option)
901 if not self.cast or self.cast == type(value) then
903 elseif self.cast == "string" then
904 if type(value) == "table" then
907 elseif self.cast == "table" then
912 -- Validate the form value
913 function AbstractValue.validate(self, value)
917 AbstractValue.transform = AbstractValue.validate
921 function AbstractValue.write(self, section, value)
922 return self.map:set(section, self.option, value)
926 function AbstractValue.remove(self, section)
927 return self.map:del(section, self.option)
934 Value - A one-line value
935 maxlength: The maximum length
937 Value = class(AbstractValue)
939 function Value.__init__(self, ...)
940 AbstractValue.__init__(self, ...)
941 self.template = "cbi/value"
946 function Value.value(self, key, val)
948 table.insert(self.keylist, tostring(key))
949 table.insert(self.vallist, tostring(val))
953 -- DummyValue - This does nothing except being there
954 DummyValue = class(AbstractValue)
956 function DummyValue.__init__(self, ...)
957 AbstractValue.__init__(self, ...)
958 self.template = "cbi/dvalue"
962 function DummyValue.parse(self)
968 Flag - A flag being enabled or disabled
970 Flag = class(AbstractValue)
972 function Flag.__init__(self, ...)
973 AbstractValue.__init__(self, ...)
974 self.template = "cbi/fvalue"
980 -- A flag can only have two states: set or unset
981 function Flag.parse(self, section)
982 local fvalue = self:formvalue(section)
985 fvalue = self.enabled
987 fvalue = self.disabled
990 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
991 if not(fvalue == self:cfgvalue(section)) then
992 self:write(section, fvalue)
1002 ListValue - A one-line value predefined in a list
1003 widget: The widget that will be used (select, radio)
1005 ListValue = class(AbstractValue)
1007 function ListValue.__init__(self, ...)
1008 AbstractValue.__init__(self, ...)
1009 self.template = "cbi/lvalue"
1014 self.widget = "select"
1016 if not self.override_scheme
1017 and self.map:get_scheme(self.section.sectiontype, self.option) then
1018 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1019 if self.value and vs.values and not self.override_values then
1020 if self.rmempty or self.optional then
1023 for k, v in pairs(vs.values) do
1030 function ListValue.value(self, key, val, ...)
1032 table.insert(self.keylist, tostring(key))
1033 table.insert(self.vallist, tostring(val))
1035 for i, deps in ipairs({...}) do
1036 table.insert(self.deps, {add = "-"..key, deps=deps})
1040 function ListValue.validate(self, val)
1041 if luci.util.contains(self.keylist, val) then
1051 MultiValue - Multiple delimited values
1052 widget: The widget that will be used (select, checkbox)
1053 delimiter: The delimiter that will separate the values (default: " ")
1055 MultiValue = class(AbstractValue)
1057 function MultiValue.__init__(self, ...)
1058 AbstractValue.__init__(self, ...)
1059 self.template = "cbi/mvalue"
1064 self.widget = "checkbox"
1065 self.delimiter = " "
1068 function MultiValue.render(self, ...)
1069 if self.widget == "select" and not self.size then
1070 self.size = #self.vallist
1073 AbstractValue.render(self, ...)
1076 function MultiValue.value(self, key, val)
1078 table.insert(self.keylist, tostring(key))
1079 table.insert(self.vallist, tostring(val))
1082 function MultiValue.valuelist(self, section)
1083 local val = self:cfgvalue(section)
1085 if not(type(val) == "string") then
1089 return luci.util.split(val, self.delimiter)
1092 function MultiValue.validate(self, val)
1093 val = (type(val) == "table") and val or {val}
1097 for i, value in ipairs(val) do
1098 if luci.util.contains(self.keylist, value) then
1099 result = result and (result .. self.delimiter .. value) or value
1107 StaticList = class(MultiValue)
1109 function StaticList.__init__(self, ...)
1110 MultiValue.__init__(self, ...)
1112 self.valuelist = self.cfgvalue
1114 if not self.override_scheme
1115 and self.map:get_scheme(self.section.sectiontype, self.option) then
1116 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1117 if self.value and vs.values and not self.override_values then
1118 for k, v in pairs(vs.values) do
1125 function StaticList.validate(self, value)
1126 value = (type(value) == "table") and value or {value}
1129 for i, v in ipairs(value) do
1130 if luci.util.contains(self.valuelist, v) then
1131 table.insert(valid, v)
1138 DynamicList = class(AbstractValue)
1140 function DynamicList.__init__(self, ...)
1141 AbstractValue.__init__(self, ...)
1142 self.template = "cbi/dynlist"
1148 function DynamicList.value(self, key, val)
1150 table.insert(self.keylist, tostring(key))
1151 table.insert(self.vallist, tostring(val))
1154 function DynamicList.validate(self, value, section)
1155 value = (type(value) == "table") and value or {value}
1158 for i, v in ipairs(value) do
1160 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1161 table.insert(valid, v)
1170 TextValue - A multi-line value
1173 TextValue = class(AbstractValue)
1175 function TextValue.__init__(self, ...)
1176 AbstractValue.__init__(self, ...)
1177 self.template = "cbi/tvalue"
1183 Button = class(AbstractValue)
1185 function Button.__init__(self, ...)
1186 AbstractValue.__init__(self, ...)
1187 self.template = "cbi/button"
1188 self.inputstyle = nil