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
43 CREATE_PREFIX = "cbi.cts."
44 REMOVE_PREFIX = "cbi.rts."
46 -- Loads a CBI map from given file, creating an environment and returns it
47 function load(cbimap, ...)
50 require("luci.config")
53 local cbidir = luci.util.libpath() .. "/model/cbi/"
54 local func, err = loadfile(cbidir..cbimap..".lua")
60 luci.i18n.loadc("cbi")
62 luci.util.resfenv(func)
63 luci.util.updfenv(func, luci.cbi)
64 luci.util.extfenv(func, "translate", luci.i18n.translate)
65 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
66 luci.util.extfenv(func, "arg", {...})
70 for i, map in ipairs(maps) do
71 if not instanceof(map, Node) then
72 error("CBI map returns no valid map object!")
81 function _uvl_strip_remote_dependencies(deps)
84 for k, v in pairs(deps) do
85 k = k:gsub("%$config%.%$section%.", "")
86 if k:match("^[%w_]+$") then
95 -- Node pseudo abstract class
98 function Node.__init__(self, title, description)
100 self.title = title or ""
101 self.description = description or ""
102 self.template = "cbi/node"
106 function Node._i18n(self, config, section, option, title, description)
109 if type(luci.i18n) == "table" then
111 local key = config and config:gsub("[^%w]+", "") or ""
113 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
114 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
116 self.title = title or luci.i18n.translate( key, option or section or config )
117 self.description = description or luci.i18n.translate( key .. "_desc", "" )
121 -- Append child nodes
122 function Node.append(self, obj)
123 table.insert(self.children, obj)
126 -- Parse this node and its children
127 function Node.parse(self, ...)
128 for k, child in ipairs(self.children) do
134 function Node.render(self, scope)
138 luci.template.render(self.template, scope)
141 -- Render the children
142 function Node.render_children(self, ...)
143 for k, node in ipairs(self.children) do
150 A simple template element
152 Template = class(Node)
154 function Template.__init__(self, template)
156 self.template = template
159 function Template.render(self)
160 luci.template.render(self.template, {self=self})
165 Map - A map describing a configuration file
169 function Map.__init__(self, config, ...)
170 Node.__init__(self, ...)
171 Node._i18n(self, config, nil, nil, ...)
174 self.parsechain = {self.config}
175 self.template = "cbi/map"
176 if not uci.load_config(self.config) then
177 error("Unable to read UCI data: " .. self.config)
180 self.validator = luci.uvl.UVL()
181 self.scheme = self.validator:get_scheme(self.config)
184 function Map.get_scheme(self, sectiontype, option)
186 return self.scheme and self.scheme.sections[sectiontype]
188 return self.scheme and self.scheme.variables[sectiontype]
189 and self.scheme.variables[sectiontype][option]
193 function Map.render(self, ...)
194 if self.stateful then
195 uci.load_state(self.config)
197 uci.load_config(self.config)
199 Node.render(self, ...)
203 -- Chain foreign config
204 function Map.chain(self, config)
205 table.insert(self.parsechain, config)
208 -- Use optimized UCI writing
209 function Map.parse(self, ...)
210 if self.stateful then
211 uci.load_state(self.config)
213 uci.load_config(self.config)
216 Node.parse(self, ...)
218 for i, config in ipairs(self.parsechain) do
219 uci.save_config(config)
221 if luci.http.formvalue("cbi.apply") then
222 for i, config in ipairs(self.parsechain) do
224 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
225 luci.util.exec(luci.config.uci_oncommit[config])
228 -- Refresh data because commit changes section names
229 uci.load_config(config)
233 Node.parse(self, ...)
236 for i, config in ipairs(self.parsechain) do
241 -- Creates a child section
242 function Map.section(self, class, ...)
243 if instanceof(class, AbstractSection) then
244 local obj = class(self, ...)
248 error("class must be a descendent of AbstractSection")
253 function Map.add(self, sectiontype)
254 return uci.add(self.config, sectiontype)
258 function Map.set(self, section, option, value)
260 return uci.set(self.config, section, option, value)
262 return uci.set(self.config, section, value)
267 function Map.del(self, section, option)
269 return uci.delete(self.config, section, option)
271 return uci.delete(self.config, section)
276 function Map.get(self, section, option)
278 return uci.get_all(self.config)
280 return uci.get(self.config, section, option)
282 return uci.get_all(self.config, section)
292 Page.__init__ = Node.__init__
293 Page.parse = function() end
297 SimpleForm - A Simple non-UCI form
299 SimpleForm = class(Node)
301 function SimpleForm.__init__(self, config, title, description, data)
302 Node.__init__(self, title, description)
304 self.data = data or {}
305 self.template = "cbi/simpleform"
309 function SimpleForm.parse(self, ...)
310 if luci.http.formvalue("cbi.submit") then
311 Node.parse(self, 1, ...)
315 for k, j in ipairs(self.children) do
316 for i, v in ipairs(j.children) do
318 and (not v.tag_missing or not v.tag_missing[1])
319 and (not v.tag_invalid or not v.tag_invalid[1])
324 not luci.http.formvalue("cbi.submit") and 0
328 self.dorender = not self.handle or self:handle(state, self.data) ~= false
331 function SimpleForm.render(self, ...)
332 if self.dorender then
333 Node.render(self, ...)
337 function SimpleForm.section(self, class, ...)
338 if instanceof(class, AbstractSection) then
339 local obj = class(self, ...)
343 error("class must be a descendent of AbstractSection")
347 -- Creates a child field
348 function SimpleForm.field(self, class, ...)
350 for k, v in ipairs(self.children) do
351 if instanceof(v, SimpleSection) then
357 section = self:section(SimpleSection)
360 if instanceof(class, AbstractValue) then
361 local obj = class(self, ...)
362 obj.track_missing = true
366 error("class must be a descendent of AbstractValue")
370 function SimpleForm.set(self, section, option, value)
371 self.data[option] = value
375 function SimpleForm.del(self, section, option)
376 self.data[option] = nil
380 function SimpleForm.get(self, section, option)
381 return self.data[option]
389 AbstractSection = class(Node)
391 function AbstractSection.__init__(self, map, sectiontype, ...)
392 Node.__init__(self, ...)
393 self.sectiontype = sectiontype
395 self.config = map.config
400 self.addremove = false
404 -- Appends a new option
405 function AbstractSection.option(self, class, option, ...)
406 -- Autodetect form UVL
407 if not class or type(class) == "boolean"
408 and self.map:get_scheme(self.sectiontype, option) then
409 local vs = self.map:get_scheme(self.sectiontype, option)
410 if vs.type == "boolean" then
412 elseif vs.type == "list" then
413 class = "DynamicList"
414 elseif vs.type == "enum" or vs.type == "reference" then
421 if instanceof(class, AbstractValue) then
422 local obj = class(self.map, self, option, ...)
424 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
428 elseif not class or type(class) == "boolean" then
429 error("No valid class was given and autodetection failed.")
431 error("class must be a descendant of AbstractValue")
435 -- Parse optional options
436 function AbstractSection.parse_optionals(self, section)
437 if not self.optional then
441 self.optionals[section] = {}
443 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
444 for k,v in ipairs(self.children) do
445 if v.optional and not v:cfgvalue(section) then
446 if field == v.option then
449 table.insert(self.optionals[section], v)
454 if field and #field > 0 and self.dynamic then
455 self:add_dynamic(field)
459 -- Add a dynamic option
460 function AbstractSection.add_dynamic(self, field, optional)
461 local o = self:option(Value, field, field)
462 o.optional = optional
465 -- Parse all dynamic options
466 function AbstractSection.parse_dynamic(self, section)
467 if not self.dynamic then
471 local arr = luci.util.clone(self:cfgvalue(section))
472 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
473 for k, v in pairs(form) do
477 for key,val in pairs(arr) do
480 for i,c in ipairs(self.children) do
481 if c.option == key then
486 if create and key:sub(1, 1) ~= "." then
487 self:add_dynamic(key, true)
492 -- Returns the section's UCI table
493 function AbstractSection.cfgvalue(self, section)
494 return self.map:get(section)
497 -- Removes the section
498 function AbstractSection.remove(self, section)
499 return self.map:del(section)
502 -- Creates the section
503 function AbstractSection.create(self, section)
507 stat = self.map:set(section, nil, self.sectiontype)
509 section = self.map:add(self.sectiontype)
514 for k,v in pairs(self.children) do
516 self.map:set(section, v.option, v.default)
520 for k,v in pairs(self.defaults) do
521 self.map:set(section, k, v)
529 SimpleSection = class(AbstractSection)
531 function SimpleSection.__init__(self, form, ...)
532 AbstractSection.__init__(self, form, nil, ...)
533 self.template = "cbi/nullsection"
537 Table = class(AbstractSection)
539 function Table.__init__(self, form, data, ...)
540 local datasource = {}
541 datasource.config = "table"
544 function datasource.get(self, section, option)
545 return data[section] and data[section][option]
548 function datasource.del(...)
552 AbstractSection.__init__(self, datasource, "table", ...)
553 self.template = "cbi/tblsection"
554 self.rowcolors = true
555 self.anonymous = true
558 function Table.parse(self)
559 for i, k in ipairs(self:cfgsections()) do
560 if luci.http.formvalue("cbi.submit") then
566 function Table.cfgsections(self)
569 for i, v in luci.util.kspairs(self.data) do
570 table.insert(sections, i)
579 NamedSection - A fixed configuration section defined by its name
581 NamedSection = class(AbstractSection)
583 function NamedSection.__init__(self, map, section, stype, ...)
584 AbstractSection.__init__(self, map, stype, ...)
585 Node._i18n(self, map.config, section, nil, ...)
588 self.addremove = false
590 -- Use defaults from UVL
591 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
592 local vs = self.map:get_scheme(self.sectiontype)
593 self.addremove = not vs.unique and not vs.required
594 self.dynamic = vs.dynamic
595 self.title = self.title or vs.title
596 self.description = self.description or vs.descr
599 self.template = "cbi/nsection"
600 self.section = section
603 function NamedSection.parse(self)
604 local s = self.section
605 local active = self:cfgvalue(s)
608 if self.addremove then
609 local path = self.config.."."..s
610 if active then -- Remove the section
611 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
614 else -- Create and apply default values
615 if luci.http.formvalue("cbi.cns."..path) then
623 AbstractSection.parse_dynamic(self, s)
624 if luci.http.formvalue("cbi.submit") then
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 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:len() > 0 then
706 crval = REMOVE_PREFIX .. self.config
707 name = luci.http.formvaluetable(crval)
708 for k,v in pairs(name) do
710 luci.util.perror(self:cfgvalue(k))
711 luci.util.perror(self:checkscope(k))
712 if self:cfgvalue(k) and self:checkscope(k) then
718 for i, k in ipairs(self:cfgsections()) do
719 AbstractSection.parse_dynamic(self, k)
720 if luci.http.formvalue("cbi.submit") then
723 AbstractSection.parse_optionals(self, k)
727 -- Verifies scope of sections
728 function TypedSection.checkscope(self, section)
729 -- Check if we are not excluded
730 if self.filter and not self:filter(section) then
734 -- Check if at least one dependency is met
735 if #self.deps > 0 and self:cfgvalue(section) then
738 for k, v in ipairs(self.deps) do
739 if self:cfgvalue(section)[v.option] == v.value then
749 return self:validate(section)
753 -- Dummy validate function
754 function TypedSection.validate(self, section)
760 AbstractValue - An abstract Value Type
761 null: Value can be empty
762 valid: A function returning the value if it is valid otherwise nil
763 depends: A table of option => value pairs of which one must be true
764 default: The default value
765 size: The size of the input fields
766 rmempty: Unset value if empty
767 optional: This value is optional (see AbstractSection.optionals)
769 AbstractValue = class(Node)
771 function AbstractValue.__init__(self, map, section, option, ...)
772 Node.__init__(self, ...)
773 self.section = section
776 self.config = map.config
777 self.tag_invalid = {}
778 self.tag_missing = {}
783 self.track_missing = false
787 self.optional = false
789 -- Use defaults from UVL
790 if not self.override_scheme
791 and self.map:get_scheme(self.section.sectiontype, self.option) then
792 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
793 self.rmempty = not vs.required
794 self.cast = (vs.type == "list") and "list" or "string"
795 self.title = self.title or vs.title
796 self.description = self.description or vs.descr
798 if vs.depends and not self.override_dependencies then
799 for i, deps in ipairs(vs.depends) do
800 deps = _uvl_strip_remote_dependencies(deps)
807 if self.value and vs.values and not self.override_values then
808 for k, v in pairs(vs.values) do
815 -- Add a dependencie to another section field
816 function AbstractValue.depends(self, field, value)
818 if type(field) == "string" then
825 table.insert(self.deps, {deps=deps, add=""})
828 -- Generates the unique CBID
829 function AbstractValue.cbid(self, section)
830 return "cbid."..self.map.config.."."..section.."."..self.option
833 -- Return whether this object should be created
834 function AbstractValue.formcreated(self, section)
835 local key = "cbi.opt."..self.config.."."..section
836 return (luci.http.formvalue(key) == self.option)
839 -- Returns the formvalue for this object
840 function AbstractValue.formvalue(self, section)
841 return luci.http.formvalue(self:cbid(section))
844 function AbstractValue.additional(self, value)
845 self.optional = value
848 function AbstractValue.mandatory(self, value)
849 self.rmempty = not value
852 function AbstractValue.parse(self, section)
853 local fvalue = self:formvalue(section)
854 local cvalue = self:cfgvalue(section)
856 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
857 fvalue = self:transform(self:validate(fvalue, section))
859 self.tag_invalid[section] = true
861 if fvalue and not (fvalue == cvalue) then
862 self:write(section, fvalue)
864 else -- Unset the UCI or error
865 if self.rmempty or self.optional then
867 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
868 self.tag_missing[section] = true
873 -- Render if this value exists or if it is mandatory
874 function AbstractValue.render(self, s, scope)
875 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
878 scope.cbid = self:cbid(s)
879 scope.striptags = luci.util.striptags
881 scope.ifattr = function(cond,key,val)
883 return string.format(
884 ' %s="%s"', tostring(key),
885 luci.util.pcdata(tostring( val
887 or (type(self[key]) ~= "function" and self[key])
895 scope.attr = function(...)
896 return scope.ifattr( true, ... )
899 Node.render(self, scope)
903 -- Return the UCI value of this object
904 function AbstractValue.cfgvalue(self, section)
905 local value = self.map:get(section, self.option)
906 if not self.cast or self.cast == type(value) then
908 elseif self.cast == "string" then
909 if type(value) == "table" then
912 elseif self.cast == "table" then
917 -- Validate the form value
918 function AbstractValue.validate(self, value)
922 AbstractValue.transform = AbstractValue.validate
926 function AbstractValue.write(self, section, value)
927 return self.map:set(section, self.option, value)
931 function AbstractValue.remove(self, section)
932 return self.map:del(section, self.option)
939 Value - A one-line value
940 maxlength: The maximum length
942 Value = class(AbstractValue)
944 function Value.__init__(self, ...)
945 AbstractValue.__init__(self, ...)
946 self.template = "cbi/value"
951 function Value.value(self, key, val)
953 table.insert(self.keylist, tostring(key))
954 table.insert(self.vallist, tostring(val))
958 -- DummyValue - This does nothing except being there
959 DummyValue = class(AbstractValue)
961 function DummyValue.__init__(self, ...)
962 AbstractValue.__init__(self, ...)
963 self.template = "cbi/dvalue"
967 function DummyValue.parse(self)
973 Flag - A flag being enabled or disabled
975 Flag = class(AbstractValue)
977 function Flag.__init__(self, ...)
978 AbstractValue.__init__(self, ...)
979 self.template = "cbi/fvalue"
985 -- A flag can only have two states: set or unset
986 function Flag.parse(self, section)
987 local fvalue = self:formvalue(section)
990 fvalue = self.enabled
992 fvalue = self.disabled
995 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
996 if not(fvalue == self:cfgvalue(section)) then
997 self:write(section, fvalue)
1000 self:remove(section)
1007 ListValue - A one-line value predefined in a list
1008 widget: The widget that will be used (select, radio)
1010 ListValue = class(AbstractValue)
1012 function ListValue.__init__(self, ...)
1013 AbstractValue.__init__(self, ...)
1014 self.template = "cbi/lvalue"
1019 self.widget = "select"
1022 function ListValue.value(self, key, val, ...)
1024 table.insert(self.keylist, tostring(key))
1025 table.insert(self.vallist, tostring(val))
1027 for i, deps in ipairs({...}) do
1028 table.insert(self.deps, {add = "-"..key, deps=deps})
1032 function ListValue.validate(self, val)
1033 if luci.util.contains(self.keylist, val) then
1043 MultiValue - Multiple delimited values
1044 widget: The widget that will be used (select, checkbox)
1045 delimiter: The delimiter that will separate the values (default: " ")
1047 MultiValue = class(AbstractValue)
1049 function MultiValue.__init__(self, ...)
1050 AbstractValue.__init__(self, ...)
1051 self.template = "cbi/mvalue"
1055 self.widget = "checkbox"
1056 self.delimiter = " "
1059 function MultiValue.render(self, ...)
1060 if self.widget == "select" and not self.size then
1061 self.size = #self.vallist
1064 AbstractValue.render(self, ...)
1067 function MultiValue.value(self, key, val)
1069 table.insert(self.keylist, tostring(key))
1070 table.insert(self.vallist, tostring(val))
1073 function MultiValue.valuelist(self, section)
1074 local val = self:cfgvalue(section)
1076 if not(type(val) == "string") then
1080 return luci.util.split(val, self.delimiter)
1083 function MultiValue.validate(self, val)
1084 val = (type(val) == "table") and val or {val}
1088 for i, value in ipairs(val) do
1089 if luci.util.contains(self.keylist, value) then
1090 result = result and (result .. self.delimiter .. value) or value
1098 StaticList = class(MultiValue)
1100 function StaticList.__init__(self, ...)
1101 MultiValue.__init__(self, ...)
1103 self.valuelist = self.cfgvalue
1106 function StaticList.validate(self, value)
1107 value = (type(value) == "table") and value or {value}
1110 for i, v in ipairs(value) do
1111 if luci.util.contains(self.valuelist, v) then
1112 table.insert(valid, v)
1119 DynamicList = class(AbstractValue)
1121 function DynamicList.__init__(self, ...)
1122 AbstractValue.__init__(self, ...)
1123 self.template = "cbi/dynlist"
1130 function DynamicList.value(self, key, val)
1132 table.insert(self.keylist, tostring(key))
1133 table.insert(self.vallist, tostring(val))
1136 function DynamicList.validate(self, value, section)
1137 value = (type(value) == "table") and value or {value}
1140 for i, v in ipairs(value) do
1142 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1143 table.insert(valid, v)
1152 TextValue - A multi-line value
1155 TextValue = class(AbstractValue)
1157 function TextValue.__init__(self, ...)
1158 AbstractValue.__init__(self, ...)
1159 self.template = "cbi/tvalue"
1165 Button = class(AbstractValue)
1167 function Button.__init__(self, ...)
1168 AbstractValue.__init__(self, ...)
1169 self.template = "cbi/button"
1170 self.inputstyle = nil