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")
34 local uci = luci.model.uci
35 local class = luci.util.class
36 local instanceof = luci.util.instanceof
42 CREATE_PREFIX = "cbi.cts."
43 REMOVE_PREFIX = "cbi.rts."
45 -- Loads a CBI map from given file, creating an environment and returns it
46 function load(cbimap, ...)
49 require("luci.config")
52 local cbidir = luci.util.libpath() .. "/model/cbi/"
53 local func, err = loadfile(cbidir..cbimap..".lua")
59 luci.i18n.loadc("cbi")
61 luci.util.resfenv(func)
62 luci.util.updfenv(func, luci.cbi)
63 luci.util.extfenv(func, "translate", luci.i18n.translate)
64 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
65 luci.util.extfenv(func, "arg", {...})
69 for i, map in ipairs(maps) do
70 if not instanceof(map, Node) then
71 error("CBI map returns no valid map object!")
79 -- Node pseudo abstract class
82 function Node.__init__(self, title, description)
84 self.title = title or ""
85 self.description = description or ""
86 self.template = "cbi/node"
90 function Node._i18n(self, config, section, option, title, description)
93 if type(luci.i18n) == "table" then
95 local key = config and config:gsub("[^%w]+", "") or ""
97 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
98 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
100 self.title = title or luci.i18n.translate( key, option or section or config )
101 self.description = description or luci.i18n.translate( key .. "_desc", "" )
105 -- Append child nodes
106 function Node.append(self, obj)
107 table.insert(self.children, obj)
110 -- Parse this node and its children
111 function Node.parse(self, ...)
112 for k, child in ipairs(self.children) do
118 function Node.render(self, scope)
122 luci.template.render(self.template, scope)
125 -- Render the children
126 function Node.render_children(self, ...)
127 for k, node in ipairs(self.children) do
134 A simple template element
136 Template = class(Node)
138 function Template.__init__(self, template)
140 self.template = template
143 function Template.render(self)
144 luci.template.render(self.template, {self=self})
149 Map - A map describing a configuration file
153 function Map.__init__(self, config, ...)
154 Node.__init__(self, ...)
155 Node._i18n(self, config, nil, nil, ...)
158 self.parsechain = {self.config}
159 self.template = "cbi/map"
160 if not uci.load_config(self.config) then
161 error("Unable to read UCI data: " .. self.config)
165 function Map.render(self, ...)
166 if self.stateful then
167 uci.load_state(self.config)
169 uci.load_config(self.config)
171 Node.render(self, ...)
175 -- Chain foreign config
176 function Map.chain(self, config)
177 table.insert(self.parsechain, config)
180 -- Use optimized UCI writing
181 function Map.parse(self, ...)
182 if self.stateful then
183 uci.load_state(self.config)
185 uci.load_config(self.config)
188 Node.parse(self, ...)
190 for i, config in ipairs(self.parsechain) do
191 uci.save_config(config)
193 if luci.http.formvalue("cbi.apply") then
194 for i, config in ipairs(self.parsechain) do
196 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
197 luci.util.exec(luci.config.uci_oncommit[config])
200 -- Refresh data because commit changes section names
201 uci.load_config(config)
205 Node.parse(self, ...)
208 for i, config in ipairs(self.parsechain) do
213 -- Creates a child section
214 function Map.section(self, class, ...)
215 if instanceof(class, AbstractSection) then
216 local obj = class(self, ...)
220 error("class must be a descendent of AbstractSection")
225 function Map.add(self, sectiontype)
226 return uci.add(self.config, sectiontype)
230 function Map.set(self, section, option, value)
232 return uci.set(self.config, section, option, value)
234 return uci.set(self.config, section, value)
239 function Map.del(self, section, option)
241 return uci.delete(self.config, section, option)
243 return uci.delete(self.config, section)
248 function Map.get(self, section, option)
250 return uci.get_all(self.config)
252 return uci.get(self.config, section, option)
254 return uci.get_all(self.config, section)
264 Page.__init__ = Node.__init__
265 Page.parse = function() end
269 SimpleForm - A Simple non-UCI form
271 SimpleForm = class(Node)
273 function SimpleForm.__init__(self, config, title, description, data)
274 Node.__init__(self, title, description)
276 self.data = data or {}
277 self.template = "cbi/simpleform"
281 function SimpleForm.parse(self, ...)
282 if luci.http.formvalue("cbi.submit") then
283 Node.parse(self, 1, ...)
287 for k, j in ipairs(self.children) do
288 for i, v in ipairs(j.children) do
290 and (not v.tag_missing or not v.tag_missing[1])
291 and (not v.tag_invalid or not v.tag_invalid[1])
296 not luci.http.formvalue("cbi.submit") and 0
300 self.dorender = not self.handle or self:handle(state, self.data) ~= false
303 function SimpleForm.render(self, ...)
304 if self.dorender then
305 Node.render(self, ...)
309 function SimpleForm.section(self, class, ...)
310 if instanceof(class, AbstractSection) then
311 local obj = class(self, ...)
315 error("class must be a descendent of AbstractSection")
319 -- Creates a child field
320 function SimpleForm.field(self, class, ...)
322 for k, v in ipairs(self.children) do
323 if instanceof(v, SimpleSection) then
329 section = self:section(SimpleSection)
332 if instanceof(class, AbstractValue) then
333 local obj = class(self, ...)
334 obj.track_missing = true
338 error("class must be a descendent of AbstractValue")
342 function SimpleForm.set(self, section, option, value)
343 self.data[option] = value
347 function SimpleForm.del(self, section, option)
348 self.data[option] = nil
352 function SimpleForm.get(self, section, option)
353 return self.data[option]
361 AbstractSection = class(Node)
363 function AbstractSection.__init__(self, map, sectiontype, ...)
364 Node.__init__(self, ...)
365 self.sectiontype = sectiontype
367 self.config = map.config
372 self.addremove = false
376 -- Appends a new option
377 function AbstractSection.option(self, class, option, ...)
378 if instanceof(class, AbstractValue) then
379 local obj = class(self.map, option, ...)
381 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
386 error("class must be a descendent of AbstractValue")
390 -- Parse optional options
391 function AbstractSection.parse_optionals(self, section)
392 if not self.optional then
396 self.optionals[section] = {}
398 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
399 for k,v in ipairs(self.children) do
400 if v.optional and not v:cfgvalue(section) then
401 if field == v.option then
404 table.insert(self.optionals[section], v)
409 if field and #field > 0 and self.dynamic then
410 self:add_dynamic(field)
414 -- Add a dynamic option
415 function AbstractSection.add_dynamic(self, field, optional)
416 local o = self:option(Value, field, field)
417 o.optional = optional
420 -- Parse all dynamic options
421 function AbstractSection.parse_dynamic(self, section)
422 if not self.dynamic then
426 local arr = luci.util.clone(self:cfgvalue(section))
427 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
428 for k, v in pairs(form) do
432 for key,val in pairs(arr) do
435 for i,c in ipairs(self.children) do
436 if c.option == key then
441 if create and key:sub(1, 1) ~= "." then
442 self:add_dynamic(key, true)
447 -- Returns the section's UCI table
448 function AbstractSection.cfgvalue(self, section)
449 return self.map:get(section)
452 -- Removes the section
453 function AbstractSection.remove(self, section)
454 return self.map:del(section)
457 -- Creates the section
458 function AbstractSection.create(self, section)
462 stat = self.map:set(section, nil, self.sectiontype)
464 section = self.map:add(self.sectiontype)
469 for k,v in pairs(self.children) do
471 self.map:set(section, v.option, v.default)
475 for k,v in pairs(self.defaults) do
476 self.map:set(section, k, v)
484 SimpleSection = class(AbstractSection)
486 function SimpleSection.__init__(self, form, ...)
487 AbstractSection.__init__(self, form, nil, ...)
488 self.template = "cbi/nullsection"
492 Table = class(AbstractSection)
494 function Table.__init__(self, form, data, ...)
495 local datasource = {}
496 datasource.config = "table"
499 function datasource.get(self, section, option)
500 return data[section] and data[section][option]
503 function datasource.del(...)
507 AbstractSection.__init__(self, datasource, "table", ...)
508 self.template = "cbi/tblsection"
509 self.rowcolors = true
510 self.anonymous = true
513 function Table.parse(self)
514 for i, k in ipairs(self:cfgsections()) do
515 if luci.http.formvalue("cbi.submit") then
521 function Table.cfgsections(self)
524 for i, v in luci.util.kspairs(self.data) do
525 table.insert(sections, i)
534 NamedSection - A fixed configuration section defined by its name
536 NamedSection = class(AbstractSection)
538 function NamedSection.__init__(self, map, section, type, ...)
539 AbstractSection.__init__(self, map, type, ...)
540 Node._i18n(self, map.config, section, nil, ...)
542 self.template = "cbi/nsection"
543 self.section = section
544 self.addremove = false
547 function NamedSection.parse(self)
548 local s = self.section
549 local active = self:cfgvalue(s)
552 if self.addremove then
553 local path = self.config.."."..s
554 if active then -- Remove the section
555 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
558 else -- Create and apply default values
559 if luci.http.formvalue("cbi.cns."..path) then
567 AbstractSection.parse_dynamic(self, s)
568 if luci.http.formvalue("cbi.submit") then
571 AbstractSection.parse_optionals(self, s)
577 TypedSection - A (set of) configuration section(s) defined by the type
578 addremove: Defines whether the user can add/remove sections of this type
579 anonymous: Allow creating anonymous sections
580 validate: a validation function returning nil if the section is invalid
582 TypedSection = class(AbstractSection)
584 function TypedSection.__init__(self, map, type, ...)
585 AbstractSection.__init__(self, map, type, ...)
586 Node._i18n(self, map.config, type, nil, ...)
588 self.template = "cbi/tsection"
591 self.anonymous = false
594 -- Return all matching UCI sections for this TypedSection
595 function TypedSection.cfgsections(self)
597 uci.foreach(self.map.config, self.sectiontype,
599 if self:checkscope(section[".name"]) then
600 table.insert(sections, section[".name"])
607 -- Limits scope to sections that have certain option => value pairs
608 function TypedSection.depends(self, option, value)
609 table.insert(self.deps, {option=option, value=value})
612 function TypedSection.parse(self)
613 if self.addremove then
615 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
616 local name = luci.http.formvalue(crval)
617 if self.anonymous then
623 -- Ignore if it already exists
624 if self:cfgvalue(name) then
628 name = self:checkscope(name)
631 self.err_invalid = true
634 if name and name:len() > 0 then
641 crval = REMOVE_PREFIX .. self.config
642 name = luci.http.formvaluetable(crval)
643 for k,v in pairs(name) do
645 luci.util.perror(self:cfgvalue(k))
646 luci.util.perror(self:checkscope(k))
647 if self:cfgvalue(k) and self:checkscope(k) then
653 for i, k in ipairs(self:cfgsections()) do
654 AbstractSection.parse_dynamic(self, k)
655 if luci.http.formvalue("cbi.submit") then
658 AbstractSection.parse_optionals(self, k)
662 -- Verifies scope of sections
663 function TypedSection.checkscope(self, section)
664 -- Check if we are not excluded
665 if self.filter and not self:filter(section) then
669 -- Check if at least one dependency is met
670 if #self.deps > 0 and self:cfgvalue(section) then
673 for k, v in ipairs(self.deps) do
674 if self:cfgvalue(section)[v.option] == v.value then
684 return self:validate(section)
688 -- Dummy validate function
689 function TypedSection.validate(self, section)
695 AbstractValue - An abstract Value Type
696 null: Value can be empty
697 valid: A function returning the value if it is valid otherwise nil
698 depends: A table of option => value pairs of which one must be true
699 default: The default value
700 size: The size of the input fields
701 rmempty: Unset value if empty
702 optional: This value is optional (see AbstractSection.optionals)
704 AbstractValue = class(Node)
706 function AbstractValue.__init__(self, map, option, ...)
707 Node.__init__(self, ...)
710 self.config = map.config
711 self.tag_invalid = {}
712 self.tag_missing = {}
717 self.track_missing = false
721 self.optional = false
724 -- Add a dependencie to another section field
725 function AbstractValue.depends(self, field, value)
727 if type(field) == "string" then
734 table.insert(self.deps, {deps=deps, add=""})
737 -- Generates the unique CBID
738 function AbstractValue.cbid(self, section)
739 return "cbid."..self.map.config.."."..section.."."..self.option
742 -- Return whether this object should be created
743 function AbstractValue.formcreated(self, section)
744 local key = "cbi.opt."..self.config.."."..section
745 return (luci.http.formvalue(key) == self.option)
748 -- Returns the formvalue for this object
749 function AbstractValue.formvalue(self, section)
750 return luci.http.formvalue(self:cbid(section))
753 function AbstractValue.additional(self, value)
754 self.optional = value
757 function AbstractValue.mandatory(self, value)
758 self.rmempty = not value
761 function AbstractValue.parse(self, section)
762 local fvalue = self:formvalue(section)
763 local cvalue = self:cfgvalue(section)
765 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
766 fvalue = self:transform(self:validate(fvalue, section))
768 self.tag_invalid[section] = true
770 if fvalue and not (fvalue == cvalue) then
771 self:write(section, fvalue)
773 else -- Unset the UCI or error
774 if self.rmempty or self.optional then
776 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
777 self.tag_missing[section] = true
782 -- Render if this value exists or if it is mandatory
783 function AbstractValue.render(self, s, scope)
784 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
787 scope.cbid = self:cbid(s)
788 scope.striptags = luci.util.striptags
790 scope.ifattr = function(cond,key,val)
792 return string.format(
793 ' %s="%s"', tostring(key),
794 luci.util.pcdata(tostring( val
796 or (type(self[key]) ~= "function" and self[key])
804 scope.attr = function(...)
805 return scope.ifattr( true, ... )
808 Node.render(self, scope)
812 -- Return the UCI value of this object
813 function AbstractValue.cfgvalue(self, section)
814 local value = self.map:get(section, self.option)
815 if not self.cast or self.cast == type(value) then
817 elseif self.cast == "string" then
818 if type(value) == "table" then
821 elseif self.cast == "table" then
826 -- Validate the form value
827 function AbstractValue.validate(self, value)
831 AbstractValue.transform = AbstractValue.validate
835 function AbstractValue.write(self, section, value)
836 return self.map:set(section, self.option, value)
840 function AbstractValue.remove(self, section)
841 return self.map:del(section, self.option)
848 Value - A one-line value
849 maxlength: The maximum length
851 Value = class(AbstractValue)
853 function Value.__init__(self, ...)
854 AbstractValue.__init__(self, ...)
855 self.template = "cbi/value"
860 function Value.value(self, key, val)
862 table.insert(self.keylist, tostring(key))
863 table.insert(self.vallist, tostring(val))
867 -- DummyValue - This does nothing except being there
868 DummyValue = class(AbstractValue)
870 function DummyValue.__init__(self, map, ...)
871 AbstractValue.__init__(self, map, ...)
872 self.template = "cbi/dvalue"
876 function DummyValue.parse(self)
882 Flag - A flag being enabled or disabled
884 Flag = class(AbstractValue)
886 function Flag.__init__(self, ...)
887 AbstractValue.__init__(self, ...)
888 self.template = "cbi/fvalue"
894 -- A flag can only have two states: set or unset
895 function Flag.parse(self, section)
896 local fvalue = self:formvalue(section)
899 fvalue = self.enabled
901 fvalue = self.disabled
904 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
905 if not(fvalue == self:cfgvalue(section)) then
906 self:write(section, fvalue)
916 ListValue - A one-line value predefined in a list
917 widget: The widget that will be used (select, radio)
919 ListValue = class(AbstractValue)
921 function ListValue.__init__(self, ...)
922 AbstractValue.__init__(self, ...)
923 self.template = "cbi/lvalue"
928 self.widget = "select"
931 function ListValue.value(self, key, val, ...)
933 table.insert(self.keylist, tostring(key))
934 table.insert(self.vallist, tostring(val))
936 for i, deps in ipairs({...}) do
937 table.insert(self.deps, {add = "-"..key, deps=deps})
941 function ListValue.validate(self, val)
942 if luci.util.contains(self.keylist, val) then
952 MultiValue - Multiple delimited values
953 widget: The widget that will be used (select, checkbox)
954 delimiter: The delimiter that will separate the values (default: " ")
956 MultiValue = class(AbstractValue)
958 function MultiValue.__init__(self, ...)
959 AbstractValue.__init__(self, ...)
960 self.template = "cbi/mvalue"
964 self.widget = "checkbox"
968 function MultiValue.render(self, ...)
969 if self.widget == "select" and not self.size then
970 self.size = #self.vallist
973 AbstractValue.render(self, ...)
976 function MultiValue.value(self, key, val)
978 table.insert(self.keylist, tostring(key))
979 table.insert(self.vallist, tostring(val))
982 function MultiValue.valuelist(self, section)
983 local val = self:cfgvalue(section)
985 if not(type(val) == "string") then
989 return luci.util.split(val, self.delimiter)
992 function MultiValue.validate(self, val)
993 val = (type(val) == "table") and val or {val}
997 for i, value in ipairs(val) do
998 if luci.util.contains(self.keylist, value) then
999 result = result and (result .. self.delimiter .. value) or value
1007 StaticList = class(MultiValue)
1009 function StaticList.__init__(self, ...)
1010 MultiValue.__init__(self, ...)
1012 self.valuelist = self.cfgvalue
1015 function StaticList.validate(self, value)
1016 value = (type(value) == "table") and value or {value}
1019 for i, v in ipairs(value) do
1020 if luci.util.contains(self.valuelist, v) then
1021 table.insert(valid, v)
1028 DynamicList = class(AbstractValue)
1030 function DynamicList.__init__(self, ...)
1031 AbstractValue.__init__(self, ...)
1032 self.template = "cbi/dynlist"
1039 function DynamicList.value(self, key, val)
1041 table.insert(self.keylist, tostring(key))
1042 table.insert(self.vallist, tostring(val))
1045 function DynamicList.validate(self, value, section)
1046 value = (type(value) == "table") and value or {value}
1049 for i, v in ipairs(value) do
1051 not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then
1052 table.insert(valid, v)
1061 TextValue - A multi-line value
1064 TextValue = class(AbstractValue)
1066 function TextValue.__init__(self, ...)
1067 AbstractValue.__init__(self, ...)
1068 self.template = "cbi/tvalue"
1074 Button = class(AbstractValue)
1076 function Button.__init__(self, ...)
1077 AbstractValue.__init__(self, ...)
1078 self.template = "cbi/button"
1079 self.inputstyle = nil