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
39 -- Loads a CBI map from given file, creating an environment and returns it
40 function load(cbimap, ...)
43 require("luci.config")
46 local cbidir = luci.util.libpath() .. "/model/cbi/"
47 local func, err = loadfile(cbidir..cbimap..".lua")
53 luci.i18n.loadc("cbi")
55 luci.util.resfenv(func)
56 luci.util.updfenv(func, luci.cbi)
57 luci.util.extfenv(func, "translate", luci.i18n.translate)
58 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
59 luci.util.extfenv(func, "arg", {...})
63 for i, map in ipairs(maps) do
64 if not instanceof(map, Map) then
65 error("CBI map returns no valid map object!")
73 -- Node pseudo abstract class
76 function Node.__init__(self, title, description)
78 self.title = title or ""
79 self.description = description or ""
80 self.template = "cbi/node"
84 function Node._i18n(self, config, section, option, title, description)
87 if type(luci.i18n) == "table" then
89 local key = config:gsub("[^%w]+", "")
91 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
92 if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end
94 self.title = title or luci.i18n.translate( key, option or section or config )
95 self.description = description or luci.i18n.translate( key .. "_desc", "" )
100 function Node.append(self, obj)
101 table.insert(self.children, obj)
104 -- Parse this node and its children
105 function Node.parse(self, ...)
106 for k, child in ipairs(self.children) do
112 function Node.render(self, scope)
116 luci.template.render(self.template, scope)
119 -- Render the children
120 function Node.render_children(self, ...)
121 for k, node in ipairs(self.children) do
128 A simple template element
130 Template = class(Node)
132 function Template.__init__(self, template)
134 self.template = template
139 Map - A map describing a configuration file
143 function Map.__init__(self, config, ...)
144 Node.__init__(self, ...)
145 Node._i18n(self, config, nil, nil, ...)
148 self.parsechain = {self.config}
149 self.template = "cbi/map"
150 if not uci.load(self.config) then
151 error("Unable to read UCI data: " .. self.config)
156 -- Chain foreign config
157 function Map.chain(self, config)
158 table.insert(self.parsechain, config)
161 -- Use optimized UCI writing
162 function Map.parse(self, ...)
163 Node.parse(self, ...)
164 for i, config in ipairs(self.parsechain) do
167 if luci.http.formvalue("cbi.apply") then
168 for i, config in ipairs(self.parsechain) do
170 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
171 luci.util.exec(luci.config.uci_oncommit[config])
174 -- Refresh data because commit changes section names
180 Node.parse(self, ...)
183 for i, config in ipairs(self.parsechain) do
188 -- Creates a child section
189 function Map.section(self, class, ...)
190 if instanceof(class, AbstractSection) then
191 local obj = class(self, ...)
195 error("class must be a descendent of AbstractSection")
200 function Map.add(self, sectiontype)
201 return uci.add(self.config, sectiontype)
205 function Map.set(self, section, option, value)
207 return uci.set(self.config, section, option, value)
209 return uci.set(self.config, section, value)
214 function Map.del(self, section, option)
216 return uci.delete(self.config, section, option)
218 return uci.delete(self.config, section)
223 function Map.get(self, section, option)
225 return uci.get_all(self.config)
227 return uci.get(self.config, section, option)
229 return uci.get_all(self.config, section)
237 AbstractSection = class(Node)
239 function AbstractSection.__init__(self, map, sectiontype, ...)
240 Node.__init__(self, ...)
241 self.sectiontype = sectiontype
243 self.config = map.config
248 self.addremove = false
252 -- Appends a new option
253 function AbstractSection.option(self, class, option, ...)
254 if instanceof(class, AbstractValue) then
255 local obj = class(self.map, option, ...)
257 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
262 error("class must be a descendent of AbstractValue")
266 -- Parse optional options
267 function AbstractSection.parse_optionals(self, section)
268 if not self.optional then
272 self.optionals[section] = {}
274 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
275 for k,v in ipairs(self.children) do
276 if v.optional and not v:cfgvalue(section) then
277 if field == v.option then
280 table.insert(self.optionals[section], v)
285 if field and #field > 0 and self.dynamic then
286 self:add_dynamic(field)
290 -- Add a dynamic option
291 function AbstractSection.add_dynamic(self, field, optional)
292 local o = self:option(Value, field, field)
293 o.optional = optional
296 -- Parse all dynamic options
297 function AbstractSection.parse_dynamic(self, section)
298 if not self.dynamic then
302 local arr = luci.util.clone(self:cfgvalue(section))
303 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
304 for k, v in pairs(form) do
308 for key,val in pairs(arr) do
311 for i,c in ipairs(self.children) do
312 if c.option == key then
317 if create and key:sub(1, 1) ~= "." then
318 self:add_dynamic(key, true)
323 -- Returns the section's UCI table
324 function AbstractSection.cfgvalue(self, section)
325 return self.map:get(section)
328 -- Removes the section
329 function AbstractSection.remove(self, section)
330 return self.map:del(section)
333 -- Creates the section
334 function AbstractSection.create(self, section)
338 stat = self.map:set(section, nil, self.sectiontype)
340 section = self.map:add(self.sectiontype)
345 for k,v in pairs(self.children) do
347 self.map:set(section, v.option, v.default)
351 for k,v in pairs(self.defaults) do
352 self.map:set(section, k, v)
362 NamedSection - A fixed configuration section defined by its name
364 NamedSection = class(AbstractSection)
366 function NamedSection.__init__(self, map, section, type, ...)
367 AbstractSection.__init__(self, map, type, ...)
368 Node._i18n(self, map.config, section, nil, ...)
370 self.template = "cbi/nsection"
371 self.section = section
372 self.addremove = false
375 function NamedSection.parse(self)
376 local s = self.section
377 local active = self:cfgvalue(s)
380 if self.addremove then
381 local path = self.config.."."..s
382 if active then -- Remove the section
383 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
386 else -- Create and apply default values
387 if luci.http.formvalue("cbi.cns."..path) then
395 AbstractSection.parse_dynamic(self, s)
396 if luci.http.formvalue("cbi.submit") then
399 AbstractSection.parse_optionals(self, s)
405 TypedSection - A (set of) configuration section(s) defined by the type
406 addremove: Defines whether the user can add/remove sections of this type
407 anonymous: Allow creating anonymous sections
408 validate: a validation function returning nil if the section is invalid
410 TypedSection = class(AbstractSection)
412 function TypedSection.__init__(self, map, type, ...)
413 AbstractSection.__init__(self, map, type, ...)
414 Node._i18n(self, map.config, type, nil, ...)
416 self.template = "cbi/tsection"
419 self.anonymous = false
422 -- Return all matching UCI sections for this TypedSection
423 function TypedSection.cfgsections(self)
425 uci.foreach(self.map.config, self.sectiontype,
427 if self:checkscope(section[".name"]) then
428 table.insert(sections, section[".name"])
435 -- Limits scope to sections that have certain option => value pairs
436 function TypedSection.depends(self, option, value)
437 table.insert(self.deps, {option=option, value=value})
440 function TypedSection.parse(self)
441 if self.addremove then
443 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
444 local name = luci.http.formvalue(crval)
445 if self.anonymous then
451 -- Ignore if it already exists
452 if self:cfgvalue(name) then
456 name = self:checkscope(name)
459 self.err_invalid = true
462 if name and name:len() > 0 then
469 crval = "cbi.rts." .. self.config
470 name = luci.http.formvaluetable(crval)
471 for k,v in pairs(name) do
472 if self:cfgvalue(k) and self:checkscope(k) then
478 for i, k in ipairs(self:cfgsections()) do
479 AbstractSection.parse_dynamic(self, k)
480 if luci.http.formvalue("cbi.submit") then
483 AbstractSection.parse_optionals(self, k)
487 -- Verifies scope of sections
488 function TypedSection.checkscope(self, section)
489 -- Check if we are not excluded
490 if self.filter and not self:filter(section) then
494 -- Check if at least one dependency is met
495 if #self.deps > 0 and self:cfgvalue(section) then
498 for k, v in ipairs(self.deps) do
499 if self:cfgvalue(section)[v.option] == v.value then
509 return self:validate(section)
513 -- Dummy validate function
514 function TypedSection.validate(self, section)
520 AbstractValue - An abstract Value Type
521 null: Value can be empty
522 valid: A function returning the value if it is valid otherwise nil
523 depends: A table of option => value pairs of which one must be true
524 default: The default value
525 size: The size of the input fields
526 rmempty: Unset value if empty
527 optional: This value is optional (see AbstractSection.optionals)
529 AbstractValue = class(Node)
531 function AbstractValue.__init__(self, map, option, ...)
532 Node.__init__(self, ...)
535 self.config = map.config
536 self.tag_invalid = {}
542 self.optional = false
545 -- Add a dependencie to another section field
546 function AbstractValue.depends(self, field, value)
547 table.insert(self.deps, {field=field, value=value})
550 -- Return whether this object should be created
551 function AbstractValue.formcreated(self, section)
552 local key = "cbi.opt."..self.config.."."..section
553 return (luci.http.formvalue(key) == self.option)
556 -- Returns the formvalue for this object
557 function AbstractValue.formvalue(self, section)
558 local key = "cbid."..self.map.config.."."..section.."."..self.option
559 return luci.http.formvalue(key)
562 function AbstractValue.parse(self, section)
563 local fvalue = self:formvalue(section)
565 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
566 fvalue = self:validate(fvalue)
568 self.tag_invalid[section] = true
570 if fvalue and not (fvalue == self:cfgvalue(section)) then
571 self:write(section, fvalue)
573 else -- Unset the UCI or error
574 if self.rmempty or self.optional then
580 -- Render if this value exists or if it is mandatory
581 function AbstractValue.render(self, s, scope)
582 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
585 scope.cbid = "cbid." .. self.config ..
589 scope.ifattr = function(cond,key,val)
591 return string.format(
592 ' %s="%s"', tostring(key),
595 or (type(self[key]) ~= "function" and self[key])
603 scope.attr = function(...)
604 return scope.ifattr( true, ... )
607 Node.render(self, scope)
611 -- Return the UCI value of this object
612 function AbstractValue.cfgvalue(self, section)
613 return self.map:get(section, self.option)
616 -- Validate the form value
617 function AbstractValue.validate(self, value)
622 function AbstractValue.write(self, section, value)
623 return self.map:set(section, self.option, value)
627 function AbstractValue.remove(self, section)
628 return self.map:del(section, self.option)
635 Value - A one-line value
636 maxlength: The maximum length
638 Value = class(AbstractValue)
640 function Value.__init__(self, ...)
641 AbstractValue.__init__(self, ...)
642 self.template = "cbi/value"
647 function Value.value(self, key, val)
649 table.insert(self.keylist, tostring(key))
650 table.insert(self.vallist, tostring(val))
654 -- DummyValue - This does nothing except being there
655 DummyValue = class(AbstractValue)
657 function DummyValue.__init__(self, map, ...)
658 AbstractValue.__init__(self, map, ...)
659 self.template = "cbi/dvalue"
663 function DummyValue.parse(self)
669 Flag - A flag being enabled or disabled
671 Flag = class(AbstractValue)
673 function Flag.__init__(self, ...)
674 AbstractValue.__init__(self, ...)
675 self.template = "cbi/fvalue"
681 -- A flag can only have two states: set or unset
682 function Flag.parse(self, section)
683 local fvalue = self:formvalue(section)
686 fvalue = self.enabled
688 fvalue = self.disabled
691 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
692 if not(fvalue == self:cfgvalue(section)) then
693 self:write(section, fvalue)
703 ListValue - A one-line value predefined in a list
704 widget: The widget that will be used (select, radio)
706 ListValue = class(AbstractValue)
708 function ListValue.__init__(self, ...)
709 AbstractValue.__init__(self, ...)
710 self.template = "cbi/lvalue"
715 self.widget = "select"
718 function ListValue.value(self, key, val)
720 table.insert(self.keylist, tostring(key))
721 table.insert(self.vallist, tostring(val))
724 function ListValue.validate(self, val)
725 if luci.util.contains(self.keylist, val) then
735 MultiValue - Multiple delimited values
736 widget: The widget that will be used (select, checkbox)
737 delimiter: The delimiter that will separate the values (default: " ")
739 MultiValue = class(AbstractValue)
741 function MultiValue.__init__(self, ...)
742 AbstractValue.__init__(self, ...)
743 self.template = "cbi/mvalue"
747 self.widget = "checkbox"
751 function MultiValue.render(self, ...)
752 if self.widget == "select" and not self.size then
753 self.size = #self.vallist
756 AbstractValue.render(self, ...)
759 function MultiValue.value(self, key, val)
761 table.insert(self.keylist, tostring(key))
762 table.insert(self.vallist, tostring(val))
765 function MultiValue.valuelist(self, section)
766 local val = self:cfgvalue(section)
768 if not(type(val) == "string") then
772 return luci.util.split(val, self.delimiter)
775 function MultiValue.validate(self, val)
776 val = (type(val) == "table") and val or {val}
780 for i, value in ipairs(val) do
781 if luci.util.contains(self.keylist, value) then
782 result = result and (result .. self.delimiter .. value) or value