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:gsub("[^%w]+", "")
97 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
98 if option then key = key .. "_" .. 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(self.config) then
161 error("Unable to read UCI data: " .. self.config)
166 -- Chain foreign config
167 function Map.chain(self, config)
168 table.insert(self.parsechain, config)
171 -- Use optimized UCI writing
172 function Map.parse(self, ...)
173 Node.parse(self, ...)
174 for i, config in ipairs(self.parsechain) do
177 if luci.http.formvalue("cbi.apply") then
178 for i, config in ipairs(self.parsechain) do
180 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
181 luci.util.exec(luci.config.uci_oncommit[config])
184 -- Refresh data because commit changes section names
190 Node.parse(self, ...)
193 for i, config in ipairs(self.parsechain) do
198 -- Creates a child section
199 function Map.section(self, class, ...)
200 if instanceof(class, AbstractSection) then
201 local obj = class(self, ...)
205 error("class must be a descendent of AbstractSection")
210 function Map.add(self, sectiontype)
211 return uci.add(self.config, sectiontype)
215 function Map.set(self, section, option, value)
217 return uci.set(self.config, section, option, value)
219 return uci.set(self.config, section, value)
224 function Map.del(self, section, option)
226 return uci.delete(self.config, section, option)
228 return uci.delete(self.config, section)
233 function Map.get(self, section, option)
235 return uci.get_all(self.config)
237 return uci.get(self.config, section, option)
239 return uci.get_all(self.config, section)
244 function Map.stateget(self, section, option)
245 return uci.get_statevalue(self.config, section, option)
254 Page.__init__ = Node.__init__
255 Page.parse = function() end
259 SimpleForm - A Simple non-UCI form
261 SimpleForm = class(Node)
263 function SimpleForm.__init__(self, config, title, description, data)
264 Node.__init__(self, title, description)
266 self.data = data or {}
267 self.template = "cbi/simpleform"
271 function SimpleForm.parse(self, ...)
272 if luci.http.formvalue("cbi.submit") then
273 Node.parse(self, 1, ...)
277 for k, j in ipairs(self.children) do
278 for i, v in ipairs(j.children) do
280 and (not v.tag_missing or not v.tag_missing[1])
281 and (not v.tag_invalid or not v.tag_invalid[1])
286 not luci.http.formvalue("cbi.submit") and 0
290 self.dorender = self:handle(state, self.data) ~= false
293 function SimpleForm.render(self, ...)
294 if self.dorender then
295 Node.render(self, ...)
299 function SimpleForm.section(self, class, ...)
300 if instanceof(class, AbstractSection) then
301 local obj = class(self, ...)
305 error("class must be a descendent of AbstractSection")
309 -- Creates a child field
310 function SimpleForm.field(self, class, ...)
312 for k, v in ipairs(self.children) do
313 if instanceof(v, SimpleSection) then
319 section = self:section(SimpleSection)
322 if instanceof(class, AbstractValue) then
323 local obj = class(self, ...)
324 obj.track_missing = true
328 error("class must be a descendent of AbstractValue")
332 function SimpleForm.set(self, section, option, value)
333 self.data[option] = value
337 function SimpleForm.del(self, section, option)
338 self.data[option] = nil
342 function SimpleForm.get(self, section, option)
343 return self.data[option]
351 AbstractSection = class(Node)
353 function AbstractSection.__init__(self, map, sectiontype, ...)
354 Node.__init__(self, ...)
355 self.sectiontype = sectiontype
357 self.config = map.config
362 self.addremove = false
366 -- Appends a new option
367 function AbstractSection.option(self, class, option, ...)
368 if instanceof(class, AbstractValue) then
369 local obj = class(self.map, option, ...)
371 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
376 error("class must be a descendent of AbstractValue")
380 -- Parse optional options
381 function AbstractSection.parse_optionals(self, section)
382 if not self.optional then
386 self.optionals[section] = {}
388 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
389 for k,v in ipairs(self.children) do
390 if v.optional and not v:cfgvalue(section) then
391 if field == v.option then
394 table.insert(self.optionals[section], v)
399 if field and #field > 0 and self.dynamic then
400 self:add_dynamic(field)
404 -- Add a dynamic option
405 function AbstractSection.add_dynamic(self, field, optional)
406 local o = self:option(Value, field, field)
407 o.optional = optional
410 -- Parse all dynamic options
411 function AbstractSection.parse_dynamic(self, section)
412 if not self.dynamic then
416 local arr = luci.util.clone(self:cfgvalue(section))
417 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
418 for k, v in pairs(form) do
422 for key,val in pairs(arr) do
425 for i,c in ipairs(self.children) do
426 if c.option == key then
431 if create and key:sub(1, 1) ~= "." then
432 self:add_dynamic(key, true)
437 -- Returns the section's UCI table
438 function AbstractSection.cfgvalue(self, section)
439 return self.map:get(section)
442 -- Removes the section
443 function AbstractSection.remove(self, section)
444 return self.map:del(section)
447 -- Creates the section
448 function AbstractSection.create(self, section)
452 stat = self.map:set(section, nil, self.sectiontype)
454 section = self.map:add(self.sectiontype)
459 for k,v in pairs(self.children) do
461 self.map:set(section, v.option, v.default)
465 for k,v in pairs(self.defaults) do
466 self.map:set(section, k, v)
474 SimpleSection = class(AbstractSection)
476 function SimpleSection.__init__(self, form, ...)
477 AbstractSection.__init__(self, form, nil, ...)
478 self.template = "cbi/nullsection"
482 Table = class(AbstractSection)
484 function Table.__init__(self, form, data, ...)
485 local datasource = {}
488 function datasource.get(self, section, option)
492 AbstractSection.__init__(self, datasource, nil, ...)
495 function Table.cfgsections(self)
498 for i, v in pairs(self.data) do
499 table.insert(sections, i)
508 NamedSection - A fixed configuration section defined by its name
510 NamedSection = class(AbstractSection)
512 function NamedSection.__init__(self, map, section, type, ...)
513 AbstractSection.__init__(self, map, type, ...)
514 Node._i18n(self, map.config, section, nil, ...)
516 self.template = "cbi/nsection"
517 self.section = section
518 self.addremove = false
521 function NamedSection.parse(self)
522 local s = self.section
523 local active = self:cfgvalue(s)
526 if self.addremove then
527 local path = self.config.."."..s
528 if active then -- Remove the section
529 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
532 else -- Create and apply default values
533 if luci.http.formvalue("cbi.cns."..path) then
541 AbstractSection.parse_dynamic(self, s)
542 if luci.http.formvalue("cbi.submit") then
545 AbstractSection.parse_optionals(self, s)
551 TypedSection - A (set of) configuration section(s) defined by the type
552 addremove: Defines whether the user can add/remove sections of this type
553 anonymous: Allow creating anonymous sections
554 validate: a validation function returning nil if the section is invalid
556 TypedSection = class(AbstractSection)
558 function TypedSection.__init__(self, map, type, ...)
559 AbstractSection.__init__(self, map, type, ...)
560 Node._i18n(self, map.config, type, nil, ...)
562 self.template = "cbi/tsection"
565 self.anonymous = false
568 -- Return all matching UCI sections for this TypedSection
569 function TypedSection.cfgsections(self)
571 uci.foreach(self.map.config, self.sectiontype,
573 if self:checkscope(section[".name"]) then
574 table.insert(sections, section[".name"])
581 -- Limits scope to sections that have certain option => value pairs
582 function TypedSection.depends(self, option, value)
583 table.insert(self.deps, {option=option, value=value})
586 function TypedSection.parse(self)
587 if self.addremove then
589 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
590 local name = luci.http.formvalue(crval)
591 if self.anonymous then
597 -- Ignore if it already exists
598 if self:cfgvalue(name) then
602 name = self:checkscope(name)
605 self.err_invalid = true
608 if name and name:len() > 0 then
615 crval = REMOVE_PREFIX .. self.config
616 name = luci.http.formvaluetable(crval)
617 for k,v in pairs(name) do
618 if self:cfgvalue(k) and self:checkscope(k) then
624 for i, k in ipairs(self:cfgsections()) do
625 AbstractSection.parse_dynamic(self, k)
626 if luci.http.formvalue("cbi.submit") then
629 AbstractSection.parse_optionals(self, k)
633 -- Verifies scope of sections
634 function TypedSection.checkscope(self, section)
635 -- Check if we are not excluded
636 if self.filter and not self:filter(section) then
640 -- Check if at least one dependency is met
641 if #self.deps > 0 and self:cfgvalue(section) then
644 for k, v in ipairs(self.deps) do
645 if self:cfgvalue(section)[v.option] == v.value then
655 return self:validate(section)
659 -- Dummy validate function
660 function TypedSection.validate(self, section)
666 AbstractValue - An abstract Value Type
667 null: Value can be empty
668 valid: A function returning the value if it is valid otherwise nil
669 depends: A table of option => value pairs of which one must be true
670 default: The default value
671 size: The size of the input fields
672 rmempty: Unset value if empty
673 optional: This value is optional (see AbstractSection.optionals)
675 AbstractValue = class(Node)
677 function AbstractValue.__init__(self, map, option, ...)
678 Node.__init__(self, ...)
681 self.config = map.config
682 self.tag_invalid = {}
683 self.tag_missing = {}
686 self.track_missing = false
690 self.optional = false
691 self.stateful = false
694 -- Add a dependencie to another section field
695 function AbstractValue.depends(self, field, value)
696 table.insert(self.deps, {field=field, value=value})
699 -- Return whether this object should be created
700 function AbstractValue.formcreated(self, section)
701 local key = "cbi.opt."..self.config.."."..section
702 return (luci.http.formvalue(key) == self.option)
705 -- Returns the formvalue for this object
706 function AbstractValue.formvalue(self, section)
707 local key = "cbid."..self.map.config.."."..section.."."..self.option
708 return luci.http.formvalue(key)
711 function AbstractValue.additional(self, value)
712 self.optional = value
715 function AbstractValue.mandatory(self, value)
716 self.rmempty = not value
719 function AbstractValue.parse(self, section)
720 local fvalue = self:formvalue(section)
721 local cvalue = self:cfgvalue(section)
723 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
724 fvalue = self:transform(self:validate(fvalue, section))
726 self.tag_invalid[section] = true
728 if fvalue and not (fvalue == cvalue) then
729 self:write(section, fvalue)
731 else -- Unset the UCI or error
732 if self.rmempty or self.optional then
734 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
735 self.tag_missing[section] = true
740 -- Render if this value exists or if it is mandatory
741 function AbstractValue.render(self, s, scope)
742 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
745 scope.cbid = "cbid." .. self.config ..
749 scope.ifattr = function(cond,key,val)
751 return string.format(
752 ' %s="%s"', tostring(key),
753 luci.util.pcdata(tostring( val
755 or (type(self[key]) ~= "function" and self[key])
763 scope.attr = function(...)
764 return scope.ifattr( true, ... )
767 Node.render(self, scope)
771 -- Return the UCI value of this object
772 function AbstractValue.cfgvalue(self, section)
774 and self.map:stateget(section, self.option)
775 or self.map:get(section, self.option)
778 -- Validate the form value
779 function AbstractValue.validate(self, value)
783 AbstractValue.transform = AbstractValue.validate
787 function AbstractValue.write(self, section, value)
788 return self.map:set(section, self.option, value)
792 function AbstractValue.remove(self, section)
793 return self.map:del(section, self.option)
800 Value - A one-line value
801 maxlength: The maximum length
803 Value = class(AbstractValue)
805 function Value.__init__(self, ...)
806 AbstractValue.__init__(self, ...)
807 self.template = "cbi/value"
812 function Value.value(self, key, val)
814 table.insert(self.keylist, tostring(key))
815 table.insert(self.vallist, tostring(val))
819 -- DummyValue - This does nothing except being there
820 DummyValue = class(AbstractValue)
822 function DummyValue.__init__(self, map, ...)
823 AbstractValue.__init__(self, map, ...)
824 self.template = "cbi/dvalue"
828 function DummyValue.parse(self)
834 Flag - A flag being enabled or disabled
836 Flag = class(AbstractValue)
838 function Flag.__init__(self, ...)
839 AbstractValue.__init__(self, ...)
840 self.template = "cbi/fvalue"
846 -- A flag can only have two states: set or unset
847 function Flag.parse(self, section)
848 local fvalue = self:formvalue(section)
851 fvalue = self.enabled
853 fvalue = self.disabled
856 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
857 if not(fvalue == self:cfgvalue(section)) then
858 self:write(section, fvalue)
868 ListValue - A one-line value predefined in a list
869 widget: The widget that will be used (select, radio)
871 ListValue = class(AbstractValue)
873 function ListValue.__init__(self, ...)
874 AbstractValue.__init__(self, ...)
875 self.template = "cbi/lvalue"
880 self.widget = "select"
883 function ListValue.value(self, key, val)
885 table.insert(self.keylist, tostring(key))
886 table.insert(self.vallist, tostring(val))
889 function ListValue.validate(self, val)
890 if luci.util.contains(self.keylist, val) then
900 MultiValue - Multiple delimited values
901 widget: The widget that will be used (select, checkbox)
902 delimiter: The delimiter that will separate the values (default: " ")
904 MultiValue = class(AbstractValue)
906 function MultiValue.__init__(self, ...)
907 AbstractValue.__init__(self, ...)
908 self.template = "cbi/mvalue"
912 self.widget = "checkbox"
916 function MultiValue.render(self, ...)
917 if self.widget == "select" and not self.size then
918 self.size = #self.vallist
921 AbstractValue.render(self, ...)
924 function MultiValue.value(self, key, val)
926 table.insert(self.keylist, tostring(key))
927 table.insert(self.vallist, tostring(val))
930 function MultiValue.valuelist(self, section)
931 local val = self:cfgvalue(section)
933 if not(type(val) == "string") then
937 return luci.util.split(val, self.delimiter)
940 function MultiValue.validate(self, val)
941 val = (type(val) == "table") and val or {val}
945 for i, value in ipairs(val) do
946 if luci.util.contains(self.keylist, value) then
947 result = result and (result .. self.delimiter .. value) or value
955 TextValue - A multi-line value
958 TextValue = class(AbstractValue)
960 function TextValue.__init__(self, ...)
961 AbstractValue.__init__(self, ...)
962 self.template = "cbi/tvalue"