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!")
80 -- Node pseudo abstract class
83 function Node.__init__(self, title, description)
85 self.title = title or ""
86 self.description = description or ""
87 self.template = "cbi/node"
91 function Node._i18n(self, config, section, option, title, description)
94 if type(luci.i18n) == "table" then
96 local key = config and config:gsub("[^%w]+", "") or ""
98 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
99 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
101 self.title = title or luci.i18n.translate( key, option or section or config )
102 self.description = description or luci.i18n.translate( key .. "_desc", "" )
106 -- Append child nodes
107 function Node.append(self, obj)
108 table.insert(self.children, obj)
111 -- Parse this node and its children
112 function Node.parse(self, ...)
113 for k, child in ipairs(self.children) do
119 function Node.render(self, scope)
123 luci.template.render(self.template, scope)
126 -- Render the children
127 function Node.render_children(self, ...)
128 for k, node in ipairs(self.children) do
135 A simple template element
137 Template = class(Node)
139 function Template.__init__(self, template)
141 self.template = template
144 function Template.render(self)
145 luci.template.render(self.template, {self=self})
150 Map - A map describing a configuration file
154 function Map.__init__(self, config, ...)
155 Node.__init__(self, ...)
156 Node._i18n(self, config, nil, nil, ...)
159 self.parsechain = {self.config}
160 self.template = "cbi/map"
161 if not uci.load_config(self.config) then
162 error("Unable to read UCI data: " .. self.config)
165 self.validator = luci.uvl.UVL()
166 self.scheme = self.validator:get_scheme(self.config)
169 function Map.render(self, ...)
170 if self.stateful then
171 uci.load_state(self.config)
173 uci.load_config(self.config)
175 Node.render(self, ...)
179 -- Chain foreign config
180 function Map.chain(self, config)
181 table.insert(self.parsechain, config)
184 -- Use optimized UCI writing
185 function Map.parse(self, ...)
186 if self.stateful then
187 uci.load_state(self.config)
189 uci.load_config(self.config)
192 Node.parse(self, ...)
194 for i, config in ipairs(self.parsechain) do
195 uci.save_config(config)
197 if luci.http.formvalue("cbi.apply") then
198 for i, config in ipairs(self.parsechain) do
200 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
201 luci.util.exec(luci.config.uci_oncommit[config])
204 -- Refresh data because commit changes section names
205 uci.load_config(config)
209 Node.parse(self, ...)
212 for i, config in ipairs(self.parsechain) do
217 -- Creates a child section
218 function Map.section(self, class, ...)
219 if instanceof(class, AbstractSection) then
220 local obj = class(self, ...)
224 error("class must be a descendent of AbstractSection")
229 function Map.add(self, sectiontype)
230 return uci.add(self.config, sectiontype)
234 function Map.set(self, section, option, value)
236 return uci.set(self.config, section, option, value)
238 return uci.set(self.config, section, value)
243 function Map.del(self, section, option)
245 return uci.delete(self.config, section, option)
247 return uci.delete(self.config, section)
252 function Map.get(self, section, option)
254 return uci.get_all(self.config)
256 return uci.get(self.config, section, option)
258 return uci.get_all(self.config, section)
268 Page.__init__ = Node.__init__
269 Page.parse = function() end
273 SimpleForm - A Simple non-UCI form
275 SimpleForm = class(Node)
277 function SimpleForm.__init__(self, config, title, description, data)
278 Node.__init__(self, title, description)
280 self.data = data or {}
281 self.template = "cbi/simpleform"
285 function SimpleForm.parse(self, ...)
286 if luci.http.formvalue("cbi.submit") then
287 Node.parse(self, 1, ...)
291 for k, j in ipairs(self.children) do
292 for i, v in ipairs(j.children) do
294 and (not v.tag_missing or not v.tag_missing[1])
295 and (not v.tag_invalid or not v.tag_invalid[1])
300 not luci.http.formvalue("cbi.submit") and 0
304 self.dorender = not self.handle or self:handle(state, self.data) ~= false
307 function SimpleForm.render(self, ...)
308 if self.dorender then
309 Node.render(self, ...)
313 function SimpleForm.section(self, class, ...)
314 if instanceof(class, AbstractSection) then
315 local obj = class(self, ...)
319 error("class must be a descendent of AbstractSection")
323 -- Creates a child field
324 function SimpleForm.field(self, class, ...)
326 for k, v in ipairs(self.children) do
327 if instanceof(v, SimpleSection) then
333 section = self:section(SimpleSection)
336 if instanceof(class, AbstractValue) then
337 local obj = class(self, ...)
338 obj.track_missing = true
342 error("class must be a descendent of AbstractValue")
346 function SimpleForm.set(self, section, option, value)
347 self.data[option] = value
351 function SimpleForm.del(self, section, option)
352 self.data[option] = nil
356 function SimpleForm.get(self, section, option)
357 return self.data[option]
365 AbstractSection = class(Node)
367 function AbstractSection.__init__(self, map, sectiontype, ...)
368 Node.__init__(self, ...)
369 self.sectiontype = sectiontype
371 self.config = map.config
376 self.addremove = false
380 -- Appends a new option
381 function AbstractSection.option(self, class, option, ...)
382 if instanceof(class, AbstractValue) then
383 local obj = class(self.map, option, ...)
385 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
390 error("class must be a descendent of AbstractValue")
394 -- Parse optional options
395 function AbstractSection.parse_optionals(self, section)
396 if not self.optional then
400 self.optionals[section] = {}
402 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
403 for k,v in ipairs(self.children) do
404 if v.optional and not v:cfgvalue(section) then
405 if field == v.option then
408 table.insert(self.optionals[section], v)
413 if field and #field > 0 and self.dynamic then
414 self:add_dynamic(field)
418 -- Add a dynamic option
419 function AbstractSection.add_dynamic(self, field, optional)
420 local o = self:option(Value, field, field)
421 o.optional = optional
424 -- Parse all dynamic options
425 function AbstractSection.parse_dynamic(self, section)
426 if not self.dynamic then
430 local arr = luci.util.clone(self:cfgvalue(section))
431 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
432 for k, v in pairs(form) do
436 for key,val in pairs(arr) do
439 for i,c in ipairs(self.children) do
440 if c.option == key then
445 if create and key:sub(1, 1) ~= "." then
446 self:add_dynamic(key, true)
451 -- Returns the section's UCI table
452 function AbstractSection.cfgvalue(self, section)
453 return self.map:get(section)
456 -- Removes the section
457 function AbstractSection.remove(self, section)
458 return self.map:del(section)
461 -- Creates the section
462 function AbstractSection.create(self, section)
466 stat = self.map:set(section, nil, self.sectiontype)
468 section = self.map:add(self.sectiontype)
473 for k,v in pairs(self.children) do
475 self.map:set(section, v.option, v.default)
479 for k,v in pairs(self.defaults) do
480 self.map:set(section, k, v)
488 SimpleSection = class(AbstractSection)
490 function SimpleSection.__init__(self, form, ...)
491 AbstractSection.__init__(self, form, nil, ...)
492 self.template = "cbi/nullsection"
496 Table = class(AbstractSection)
498 function Table.__init__(self, form, data, ...)
499 local datasource = {}
500 datasource.config = "table"
503 function datasource.get(self, section, option)
504 return data[section] and data[section][option]
507 function datasource.del(...)
511 AbstractSection.__init__(self, datasource, "table", ...)
512 self.template = "cbi/tblsection"
513 self.rowcolors = true
514 self.anonymous = true
517 function Table.parse(self)
518 for i, k in ipairs(self:cfgsections()) do
519 if luci.http.formvalue("cbi.submit") then
525 function Table.cfgsections(self)
528 for i, v in luci.util.kspairs(self.data) do
529 table.insert(sections, i)
538 NamedSection - A fixed configuration section defined by its name
540 NamedSection = class(AbstractSection)
542 function NamedSection.__init__(self, map, section, stype, ...)
543 AbstractSection.__init__(self, map, stype, ...)
544 Node._i18n(self, map.config, section, nil, ...)
547 self.addremove = false
549 -- Use defaults from UVL
550 if self.map.scheme and self.map.scheme.sections[self.sectiontype] then
551 local vs = self.map.scheme.sections[self.sectiontype]
552 self.addremove = not vs.unique and not vs.required
553 self.dynamic = vs.dynamic
556 self.template = "cbi/nsection"
557 self.section = section
560 function NamedSection.parse(self)
561 local s = self.section
562 local active = self:cfgvalue(s)
565 if self.addremove then
566 local path = self.config.."."..s
567 if active then -- Remove the section
568 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
571 else -- Create and apply default values
572 if luci.http.formvalue("cbi.cns."..path) then
580 AbstractSection.parse_dynamic(self, s)
581 if luci.http.formvalue("cbi.submit") then
584 AbstractSection.parse_optionals(self, s)
590 TypedSection - A (set of) configuration section(s) defined by the type
591 addremove: Defines whether the user can add/remove sections of this type
592 anonymous: Allow creating anonymous sections
593 validate: a validation function returning nil if the section is invalid
595 TypedSection = class(AbstractSection)
597 function TypedSection.__init__(self, map, type, ...)
598 AbstractSection.__init__(self, map, type, ...)
599 Node._i18n(self, map.config, type, nil, ...)
601 self.template = "cbi/tsection"
603 self.anonymous = false
605 -- Use defaults from UVL
606 if self.map.scheme and self.map.scheme.sections[self.sectiontype] then
607 local vs = self.map.scheme.sections[self.sectiontype]
608 self.addremove = not vs.unique and not vs.required
609 self.dynamic = vs.dynamic
610 self.anonymous = not vs.named
614 -- Return all matching UCI sections for this TypedSection
615 function TypedSection.cfgsections(self)
617 uci.foreach(self.map.config, self.sectiontype,
619 if self:checkscope(section[".name"]) then
620 table.insert(sections, section[".name"])
627 -- Limits scope to sections that have certain option => value pairs
628 function TypedSection.depends(self, option, value)
629 table.insert(self.deps, {option=option, value=value})
632 function TypedSection.parse(self)
633 if self.addremove then
635 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
636 local name = luci.http.formvalue(crval)
637 if self.anonymous then
643 -- Ignore if it already exists
644 if self:cfgvalue(name) then
648 name = self:checkscope(name)
651 self.err_invalid = true
654 if name and name:len() > 0 then
661 crval = REMOVE_PREFIX .. self.config
662 name = luci.http.formvaluetable(crval)
663 for k,v in pairs(name) do
665 luci.util.perror(self:cfgvalue(k))
666 luci.util.perror(self:checkscope(k))
667 if self:cfgvalue(k) and self:checkscope(k) then
673 for i, k in ipairs(self:cfgsections()) do
674 AbstractSection.parse_dynamic(self, k)
675 if luci.http.formvalue("cbi.submit") then
678 AbstractSection.parse_optionals(self, k)
682 -- Verifies scope of sections
683 function TypedSection.checkscope(self, section)
684 -- Check if we are not excluded
685 if self.filter and not self:filter(section) then
689 -- Check if at least one dependency is met
690 if #self.deps > 0 and self:cfgvalue(section) then
693 for k, v in ipairs(self.deps) do
694 if self:cfgvalue(section)[v.option] == v.value then
704 return self:validate(section)
708 -- Dummy validate function
709 function TypedSection.validate(self, section)
715 AbstractValue - An abstract Value Type
716 null: Value can be empty
717 valid: A function returning the value if it is valid otherwise nil
718 depends: A table of option => value pairs of which one must be true
719 default: The default value
720 size: The size of the input fields
721 rmempty: Unset value if empty
722 optional: This value is optional (see AbstractSection.optionals)
724 AbstractValue = class(Node)
726 function AbstractValue.__init__(self, map, option, ...)
727 Node.__init__(self, ...)
730 self.config = map.config
731 self.tag_invalid = {}
732 self.tag_missing = {}
737 self.track_missing = false
741 self.optional = false
744 -- Add a dependencie to another section field
745 function AbstractValue.depends(self, field, value)
747 if type(field) == "string" then
754 table.insert(self.deps, {deps=deps, add=""})
757 -- Generates the unique CBID
758 function AbstractValue.cbid(self, section)
759 return "cbid."..self.map.config.."."..section.."."..self.option
762 -- Return whether this object should be created
763 function AbstractValue.formcreated(self, section)
764 local key = "cbi.opt."..self.config.."."..section
765 return (luci.http.formvalue(key) == self.option)
768 -- Returns the formvalue for this object
769 function AbstractValue.formvalue(self, section)
770 return luci.http.formvalue(self:cbid(section))
773 function AbstractValue.additional(self, value)
774 self.optional = value
777 function AbstractValue.mandatory(self, value)
778 self.rmempty = not value
781 function AbstractValue.parse(self, section)
782 local fvalue = self:formvalue(section)
783 local cvalue = self:cfgvalue(section)
785 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
786 fvalue = self:transform(self:validate(fvalue, section))
788 self.tag_invalid[section] = true
790 if fvalue and not (fvalue == cvalue) then
791 self:write(section, fvalue)
793 else -- Unset the UCI or error
794 if self.rmempty or self.optional then
796 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
797 self.tag_missing[section] = true
802 -- Render if this value exists or if it is mandatory
803 function AbstractValue.render(self, s, scope)
804 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
807 scope.cbid = self:cbid(s)
808 scope.striptags = luci.util.striptags
810 scope.ifattr = function(cond,key,val)
812 return string.format(
813 ' %s="%s"', tostring(key),
814 luci.util.pcdata(tostring( val
816 or (type(self[key]) ~= "function" and self[key])
824 scope.attr = function(...)
825 return scope.ifattr( true, ... )
828 Node.render(self, scope)
832 -- Return the UCI value of this object
833 function AbstractValue.cfgvalue(self, section)
834 local value = self.map:get(section, self.option)
835 if not self.cast or self.cast == type(value) then
837 elseif self.cast == "string" then
838 if type(value) == "table" then
841 elseif self.cast == "table" then
846 -- Validate the form value
847 function AbstractValue.validate(self, value)
851 AbstractValue.transform = AbstractValue.validate
855 function AbstractValue.write(self, section, value)
856 return self.map:set(section, self.option, value)
860 function AbstractValue.remove(self, section)
861 return self.map:del(section, self.option)
868 Value - A one-line value
869 maxlength: The maximum length
871 Value = class(AbstractValue)
873 function Value.__init__(self, ...)
874 AbstractValue.__init__(self, ...)
875 self.template = "cbi/value"
880 function Value.value(self, key, val)
882 table.insert(self.keylist, tostring(key))
883 table.insert(self.vallist, tostring(val))
887 -- DummyValue - This does nothing except being there
888 DummyValue = class(AbstractValue)
890 function DummyValue.__init__(self, map, ...)
891 AbstractValue.__init__(self, map, ...)
892 self.template = "cbi/dvalue"
896 function DummyValue.parse(self)
902 Flag - A flag being enabled or disabled
904 Flag = class(AbstractValue)
906 function Flag.__init__(self, ...)
907 AbstractValue.__init__(self, ...)
908 self.template = "cbi/fvalue"
914 -- A flag can only have two states: set or unset
915 function Flag.parse(self, section)
916 local fvalue = self:formvalue(section)
919 fvalue = self.enabled
921 fvalue = self.disabled
924 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
925 if not(fvalue == self:cfgvalue(section)) then
926 self:write(section, fvalue)
936 ListValue - A one-line value predefined in a list
937 widget: The widget that will be used (select, radio)
939 ListValue = class(AbstractValue)
941 function ListValue.__init__(self, ...)
942 AbstractValue.__init__(self, ...)
943 self.template = "cbi/lvalue"
948 self.widget = "select"
951 function ListValue.value(self, key, val, ...)
953 table.insert(self.keylist, tostring(key))
954 table.insert(self.vallist, tostring(val))
956 for i, deps in ipairs({...}) do
957 table.insert(self.deps, {add = "-"..key, deps=deps})
961 function ListValue.validate(self, val)
962 if luci.util.contains(self.keylist, val) then
972 MultiValue - Multiple delimited values
973 widget: The widget that will be used (select, checkbox)
974 delimiter: The delimiter that will separate the values (default: " ")
976 MultiValue = class(AbstractValue)
978 function MultiValue.__init__(self, ...)
979 AbstractValue.__init__(self, ...)
980 self.template = "cbi/mvalue"
984 self.widget = "checkbox"
988 function MultiValue.render(self, ...)
989 if self.widget == "select" and not self.size then
990 self.size = #self.vallist
993 AbstractValue.render(self, ...)
996 function MultiValue.value(self, key, val)
998 table.insert(self.keylist, tostring(key))
999 table.insert(self.vallist, tostring(val))
1002 function MultiValue.valuelist(self, section)
1003 local val = self:cfgvalue(section)
1005 if not(type(val) == "string") then
1009 return luci.util.split(val, self.delimiter)
1012 function MultiValue.validate(self, val)
1013 val = (type(val) == "table") and val or {val}
1017 for i, value in ipairs(val) do
1018 if luci.util.contains(self.keylist, value) then
1019 result = result and (result .. self.delimiter .. value) or value
1027 StaticList = class(MultiValue)
1029 function StaticList.__init__(self, ...)
1030 MultiValue.__init__(self, ...)
1032 self.valuelist = self.cfgvalue
1035 function StaticList.validate(self, value)
1036 value = (type(value) == "table") and value or {value}
1039 for i, v in ipairs(value) do
1040 if luci.util.contains(self.valuelist, v) then
1041 table.insert(valid, v)
1048 DynamicList = class(AbstractValue)
1050 function DynamicList.__init__(self, ...)
1051 AbstractValue.__init__(self, ...)
1052 self.template = "cbi/dynlist"
1059 function DynamicList.value(self, key, val)
1061 table.insert(self.keylist, tostring(key))
1062 table.insert(self.vallist, tostring(val))
1065 function DynamicList.validate(self, value, section)
1066 value = (type(value) == "table") and value or {value}
1069 for i, v in ipairs(value) do
1071 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1072 table.insert(valid, v)
1081 TextValue - A multi-line value
1084 TextValue = class(AbstractValue)
1086 function TextValue.__init__(self, ...)
1087 AbstractValue.__init__(self, ...)
1088 self.template = "cbi/tvalue"
1094 Button = class(AbstractValue)
1096 function Button.__init__(self, ...)
1097 AbstractValue.__init__(self, ...)
1098 self.template = "cbi/button"
1099 self.inputstyle = nil