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)
250 SimpleForm - A Simple non-UCI form
252 SimpleForm = class(Node)
254 function SimpleForm.__init__(self, config, title, description, data)
255 Node.__init__(self, title, description)
257 self.data = data or {}
258 self.template = "cbi/simpleform"
262 function SimpleForm.parse(self, ...)
263 Node.parse(self, 1, ...)
266 for i, v in ipairs(self.children) do
268 and (not v.tag_missing or not v.tag_missing[1])
269 and (not v.tag_invalid or not v.tag_invalid[1])
273 not luci.http.formvalue("cbi.submit") and 0
277 self.dorender = self:handle(state, self.data)
280 function SimpleForm.render(self, ...)
281 if self.dorender then
282 Node.render(self, ...)
286 -- Creates a child section
287 function SimpleForm.field(self, class, ...)
288 if instanceof(class, AbstractValue) then
289 local obj = class(self, ...)
293 error("class must be a descendent of AbstractValue")
297 function SimpleForm.set(self, section, option, value)
298 self.data[option] = value
302 function SimpleForm.del(self, section, option)
303 self.data[option] = nil
307 function SimpleForm.get(self, section, option)
308 return self.data[option]
316 AbstractSection = class(Node)
318 function AbstractSection.__init__(self, map, sectiontype, ...)
319 Node.__init__(self, ...)
320 self.sectiontype = sectiontype
322 self.config = map.config
327 self.addremove = false
331 -- Appends a new option
332 function AbstractSection.option(self, class, option, ...)
333 if instanceof(class, AbstractValue) then
334 local obj = class(self.map, option, ...)
336 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
341 error("class must be a descendent of AbstractValue")
345 -- Parse optional options
346 function AbstractSection.parse_optionals(self, section)
347 if not self.optional then
351 self.optionals[section] = {}
353 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
354 for k,v in ipairs(self.children) do
355 if v.optional and not v:cfgvalue(section) then
356 if field == v.option then
359 table.insert(self.optionals[section], v)
364 if field and #field > 0 and self.dynamic then
365 self:add_dynamic(field)
369 -- Add a dynamic option
370 function AbstractSection.add_dynamic(self, field, optional)
371 local o = self:option(Value, field, field)
372 o.optional = optional
375 -- Parse all dynamic options
376 function AbstractSection.parse_dynamic(self, section)
377 if not self.dynamic then
381 local arr = luci.util.clone(self:cfgvalue(section))
382 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
383 for k, v in pairs(form) do
387 for key,val in pairs(arr) do
390 for i,c in ipairs(self.children) do
391 if c.option == key then
396 if create and key:sub(1, 1) ~= "." then
397 self:add_dynamic(key, true)
402 -- Returns the section's UCI table
403 function AbstractSection.cfgvalue(self, section)
404 return self.map:get(section)
407 -- Removes the section
408 function AbstractSection.remove(self, section)
409 return self.map:del(section)
412 -- Creates the section
413 function AbstractSection.create(self, section)
417 stat = self.map:set(section, nil, self.sectiontype)
419 section = self.map:add(self.sectiontype)
424 for k,v in pairs(self.children) do
426 self.map:set(section, v.option, v.default)
430 for k,v in pairs(self.defaults) do
431 self.map:set(section, k, v)
441 NamedSection - A fixed configuration section defined by its name
443 NamedSection = class(AbstractSection)
445 function NamedSection.__init__(self, map, section, type, ...)
446 AbstractSection.__init__(self, map, type, ...)
447 Node._i18n(self, map.config, section, nil, ...)
449 self.template = "cbi/nsection"
450 self.section = section
451 self.addremove = false
454 function NamedSection.parse(self)
455 local s = self.section
456 local active = self:cfgvalue(s)
459 if self.addremove then
460 local path = self.config.."."..s
461 if active then -- Remove the section
462 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
465 else -- Create and apply default values
466 if luci.http.formvalue("cbi.cns."..path) then
474 AbstractSection.parse_dynamic(self, s)
475 if luci.http.formvalue("cbi.submit") then
478 AbstractSection.parse_optionals(self, s)
484 TypedSection - A (set of) configuration section(s) defined by the type
485 addremove: Defines whether the user can add/remove sections of this type
486 anonymous: Allow creating anonymous sections
487 validate: a validation function returning nil if the section is invalid
489 TypedSection = class(AbstractSection)
491 function TypedSection.__init__(self, map, type, ...)
492 AbstractSection.__init__(self, map, type, ...)
493 Node._i18n(self, map.config, type, nil, ...)
495 self.template = "cbi/tsection"
498 self.anonymous = false
501 -- Return all matching UCI sections for this TypedSection
502 function TypedSection.cfgsections(self)
504 uci.foreach(self.map.config, self.sectiontype,
506 if self:checkscope(section[".name"]) then
507 table.insert(sections, section[".name"])
514 -- Limits scope to sections that have certain option => value pairs
515 function TypedSection.depends(self, option, value)
516 table.insert(self.deps, {option=option, value=value})
519 function TypedSection.parse(self)
520 if self.addremove then
522 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
523 local name = luci.http.formvalue(crval)
524 if self.anonymous then
530 -- Ignore if it already exists
531 if self:cfgvalue(name) then
535 name = self:checkscope(name)
538 self.err_invalid = true
541 if name and name:len() > 0 then
548 crval = REMOVE_PREFIX .. self.config
549 name = luci.http.formvaluetable(crval)
550 for k,v in pairs(name) do
551 if self:cfgvalue(k) and self:checkscope(k) then
557 for i, k in ipairs(self:cfgsections()) do
558 AbstractSection.parse_dynamic(self, k)
559 if luci.http.formvalue("cbi.submit") then
562 AbstractSection.parse_optionals(self, k)
566 -- Verifies scope of sections
567 function TypedSection.checkscope(self, section)
568 -- Check if we are not excluded
569 if self.filter and not self:filter(section) then
573 -- Check if at least one dependency is met
574 if #self.deps > 0 and self:cfgvalue(section) then
577 for k, v in ipairs(self.deps) do
578 if self:cfgvalue(section)[v.option] == v.value then
588 return self:validate(section)
592 -- Dummy validate function
593 function TypedSection.validate(self, section)
599 AbstractValue - An abstract Value Type
600 null: Value can be empty
601 valid: A function returning the value if it is valid otherwise nil
602 depends: A table of option => value pairs of which one must be true
603 default: The default value
604 size: The size of the input fields
605 rmempty: Unset value if empty
606 optional: This value is optional (see AbstractSection.optionals)
608 AbstractValue = class(Node)
610 function AbstractValue.__init__(self, map, option, ...)
611 Node.__init__(self, ...)
614 self.config = map.config
615 self.tag_invalid = {}
616 self.tag_missing = {}
622 self.optional = false
623 self.stateful = false
626 -- Add a dependencie to another section field
627 function AbstractValue.depends(self, field, value)
628 table.insert(self.deps, {field=field, value=value})
631 -- Return whether this object should be created
632 function AbstractValue.formcreated(self, section)
633 local key = "cbi.opt."..self.config.."."..section
634 return (luci.http.formvalue(key) == self.option)
637 -- Returns the formvalue for this object
638 function AbstractValue.formvalue(self, section)
639 local key = "cbid."..self.map.config.."."..section.."."..self.option
640 return luci.http.formvalue(key)
643 function AbstractValue.additional(self, value)
644 self.optional = value
647 function AbstractValue.mandatory(self, value)
648 self.rmempty = not value
651 function AbstractValue.parse(self, section)
652 local fvalue = self:formvalue(section)
653 local cvalue = self:cfgvalue(section)
655 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
656 fvalue = self:transform(self:validate(fvalue))
658 self.tag_invalid[section] = true
660 if fvalue and not (fvalue == self:cfgvalue(section)) then
661 self:write(section, fvalue)
663 else -- Unset the UCI or error
664 if self.rmempty or self.optional then
666 elseif not fvalue or fvalue ~= cvalue then
667 --self.tag_missing[section] = true
672 -- Render if this value exists or if it is mandatory
673 function AbstractValue.render(self, s, scope)
674 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
677 scope.cbid = "cbid." .. self.config ..
681 scope.ifattr = function(cond,key,val)
683 return string.format(
684 ' %s="%s"', tostring(key),
687 or (type(self[key]) ~= "function" and self[key])
695 scope.attr = function(...)
696 return scope.ifattr( true, ... )
699 Node.render(self, scope)
703 -- Return the UCI value of this object
704 function AbstractValue.cfgvalue(self, section)
706 and self.map:stateget(section, self.option)
707 or self.map:get(section, self.option)
710 -- Validate the form value
711 function AbstractValue.validate(self, value)
715 AbstractValue.transform = AbstractValue.validate
719 function AbstractValue.write(self, section, value)
720 return self.map:set(section, self.option, value)
724 function AbstractValue.remove(self, section)
725 return self.map:del(section, self.option)
732 Value - A one-line value
733 maxlength: The maximum length
735 Value = class(AbstractValue)
737 function Value.__init__(self, ...)
738 AbstractValue.__init__(self, ...)
739 self.template = "cbi/value"
744 function Value.value(self, key, val)
746 table.insert(self.keylist, tostring(key))
747 table.insert(self.vallist, tostring(val))
751 -- DummyValue - This does nothing except being there
752 DummyValue = class(AbstractValue)
754 function DummyValue.__init__(self, map, ...)
755 AbstractValue.__init__(self, map, ...)
756 self.template = "cbi/dvalue"
760 function DummyValue.parse(self)
766 Flag - A flag being enabled or disabled
768 Flag = class(AbstractValue)
770 function Flag.__init__(self, ...)
771 AbstractValue.__init__(self, ...)
772 self.template = "cbi/fvalue"
778 -- A flag can only have two states: set or unset
779 function Flag.parse(self, section)
780 local fvalue = self:formvalue(section)
783 fvalue = self.enabled
785 fvalue = self.disabled
788 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
789 if not(fvalue == self:cfgvalue(section)) then
790 self:write(section, fvalue)
800 ListValue - A one-line value predefined in a list
801 widget: The widget that will be used (select, radio)
803 ListValue = class(AbstractValue)
805 function ListValue.__init__(self, ...)
806 AbstractValue.__init__(self, ...)
807 self.template = "cbi/lvalue"
812 self.widget = "select"
815 function ListValue.value(self, key, val)
817 table.insert(self.keylist, tostring(key))
818 table.insert(self.vallist, tostring(val))
821 function ListValue.validate(self, val)
822 if luci.util.contains(self.keylist, val) then
832 MultiValue - Multiple delimited values
833 widget: The widget that will be used (select, checkbox)
834 delimiter: The delimiter that will separate the values (default: " ")
836 MultiValue = class(AbstractValue)
838 function MultiValue.__init__(self, ...)
839 AbstractValue.__init__(self, ...)
840 self.template = "cbi/mvalue"
844 self.widget = "checkbox"
848 function MultiValue.render(self, ...)
849 if self.widget == "select" and not self.size then
850 self.size = #self.vallist
853 AbstractValue.render(self, ...)
856 function MultiValue.value(self, key, val)
858 table.insert(self.keylist, tostring(key))
859 table.insert(self.vallist, tostring(val))
862 function MultiValue.valuelist(self, section)
863 local val = self:cfgvalue(section)
865 if not(type(val) == "string") then
869 return luci.util.split(val, self.delimiter)
872 function MultiValue.validate(self, val)
873 val = (type(val) == "table") and val or {val}
877 for i, value in ipairs(val) do
878 if luci.util.contains(self.keylist, value) then
879 result = result and (result .. self.delimiter .. value) or value
887 TextValue - A multi-line value
890 TextValue = class(AbstractValue)
892 function TextValue.__init__(self, ...)
893 AbstractValue.__init__(self, ...)
894 self.template = "cbi/tvalue"