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
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)
62 for i, map in ipairs(maps) do
63 if not instanceof(map, Map) then
64 error("CBI map returns no valid map object!")
72 -- Node pseudo abstract class
75 function Node.__init__(self, title, description)
77 self.title = title or ""
78 self.description = description or ""
79 self.template = "cbi/node"
83 function Node._i18n(self, config, section, option, title, description)
86 if type(luci.i18n) == "table" then
88 local key = config:gsub("[^%w]+", "")
90 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
91 if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end
93 self.title = title or luci.i18n.translate( key, option or section or config )
94 self.description = description or luci.i18n.translate( key .. "_desc", "" )
99 function Node.append(self, obj)
100 table.insert(self.children, obj)
103 -- Parse this node and its children
104 function Node.parse(self, ...)
105 for k, child in ipairs(self.children) do
111 function Node.render(self, scope)
115 luci.template.render(self.template, scope)
118 -- Render the children
119 function Node.render_children(self, ...)
120 for k, node in ipairs(self.children) do
127 A simple template element
129 Template = class(Node)
131 function Template.__init__(self, template)
133 self.template = template
138 Map - A map describing a configuration file
142 function Map.__init__(self, config, ...)
143 Node.__init__(self, ...)
144 Node._i18n(self, config, nil, nil, ...)
147 self.parsechain = {self.config}
148 self.template = "cbi/map"
149 if not uci.load(self.config) then
150 error("Unable to read UCI data: " .. self.config)
155 -- Chain foreign config
156 function Map.chain(self, config)
157 table.insert(self.parsechain, config)
160 -- Use optimized UCI writing
161 function Map.parse(self, ...)
162 Node.parse(self, ...)
163 for i, config in ipairs(self.parsechain) do
166 if luci.http.formvalue("cbi.apply") then
167 for i, config in ipairs(self.parsechain) do
169 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
170 luci.util.exec(luci.config.uci_oncommit[config])
173 -- Refresh data because commit changes section names
179 Node.parse(self, ...)
182 for i, config in ipairs(self.parsechain) do
187 -- Creates a child section
188 function Map.section(self, class, ...)
189 if instanceof(class, AbstractSection) then
190 local obj = class(self, ...)
194 error("class must be a descendent of AbstractSection")
199 function Map.add(self, sectiontype)
200 return uci.add(self.config, sectiontype)
204 function Map.set(self, section, option, value)
206 return uci.set(self.config, section, option, value)
208 return uci.set(self.config, section, value)
213 function Map.del(self, section, option)
215 return uci.delete(self.config, section, option)
217 return uci.delete(self.config, section)
222 function Map.get(self, section, option)
224 return uci.get_all(self.config)
226 return uci.get(self.config, section, option)
228 return uci.get_all(self.config, section)
236 AbstractSection = class(Node)
238 function AbstractSection.__init__(self, map, sectiontype, ...)
239 Node.__init__(self, ...)
240 self.sectiontype = sectiontype
242 self.config = map.config
247 self.addremove = false
251 -- Appends a new option
252 function AbstractSection.option(self, class, option, ...)
253 if instanceof(class, AbstractValue) then
254 local obj = class(self.map, option, ...)
256 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
261 error("class must be a descendent of AbstractValue")
265 -- Parse optional options
266 function AbstractSection.parse_optionals(self, section)
267 if not self.optional then
271 self.optionals[section] = {}
273 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
274 for k,v in ipairs(self.children) do
275 if v.optional and not v:cfgvalue(section) then
276 if field == v.option then
279 table.insert(self.optionals[section], v)
284 if field and #field > 0 and self.dynamic then
285 self:add_dynamic(field)
289 -- Add a dynamic option
290 function AbstractSection.add_dynamic(self, field, optional)
291 local o = self:option(Value, field, field)
292 o.optional = optional
295 -- Parse all dynamic options
296 function AbstractSection.parse_dynamic(self, section)
297 if not self.dynamic then
301 local arr = luci.util.clone(self:cfgvalue(section))
302 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
303 for k, v in pairs(form) do
307 for key,val in pairs(arr) do
310 for i,c in ipairs(self.children) do
311 if c.option == key then
316 if create and key:sub(1, 1) ~= "." then
317 self:add_dynamic(key, true)
322 -- Returns the section's UCI table
323 function AbstractSection.cfgvalue(self, section)
324 return self.map:get(section)
327 -- Removes the section
328 function AbstractSection.remove(self, section)
329 return self.map:del(section)
332 -- Creates the section
333 function AbstractSection.create(self, section)
337 stat = self.map:set(section, nil, self.sectiontype)
339 section = self.map:add(self.sectiontype)
344 for k,v in pairs(self.children) do
346 self.map:set(section, v.option, v.default)
350 for k,v in pairs(self.defaults) do
351 self.map:set(section, k, v)
361 NamedSection - A fixed configuration section defined by its name
363 NamedSection = class(AbstractSection)
365 function NamedSection.__init__(self, map, section, type, ...)
366 AbstractSection.__init__(self, map, type, ...)
367 Node._i18n(self, map.config, section, nil, ...)
369 self.template = "cbi/nsection"
370 self.section = section
371 self.addremove = false
374 function NamedSection.parse(self)
375 local s = self.section
376 local active = self:cfgvalue(s)
379 if self.addremove then
380 local path = self.config.."."..s
381 if active then -- Remove the section
382 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
385 else -- Create and apply default values
386 if luci.http.formvalue("cbi.cns."..path) then
394 AbstractSection.parse_dynamic(self, s)
395 if luci.http.formvalue("cbi.submit") then
398 AbstractSection.parse_optionals(self, s)
404 TypedSection - A (set of) configuration section(s) defined by the type
405 addremove: Defines whether the user can add/remove sections of this type
406 anonymous: Allow creating anonymous sections
407 validate: a validation function returning nil if the section is invalid
409 TypedSection = class(AbstractSection)
411 function TypedSection.__init__(self, map, type, ...)
412 AbstractSection.__init__(self, map, type, ...)
413 Node._i18n(self, map.config, type, nil, ...)
415 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 -- Excludes several sections by name
441 function TypedSection.exclude(self, field)
442 self.excludes[field] = true
445 function TypedSection.parse(self)
446 if self.addremove then
448 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
449 local name = luci.http.formvalue(crval)
450 if self.anonymous then
456 -- Ignore if it already exists
457 if self:cfgvalue(name) then
461 name = self:checkscope(name)
464 self.err_invalid = true
467 if name and name:len() > 0 then
474 crval = "cbi.rts." .. self.config
475 name = luci.http.formvaluetable(crval)
476 for k,v in pairs(name) do
477 if self:cfgvalue(k) and self:checkscope(k) then
483 for i, k in ipairs(self:cfgsections()) do
484 AbstractSection.parse_dynamic(self, k)
485 if luci.http.formvalue("cbi.submit") then
488 AbstractSection.parse_optionals(self, k)
492 -- Verifies scope of sections
493 function TypedSection.checkscope(self, section)
494 -- Check if we are not excluded
495 if self.excludes[section] then
499 -- Check if at least one dependency is met
500 if #self.deps > 0 and self:cfgvalue(section) then
503 for k, v in ipairs(self.deps) do
504 if self:cfgvalue(section)[v.option] == v.value then
514 return self:validate(section)
518 -- Dummy validate function
519 function TypedSection.validate(self, section)
525 AbstractValue - An abstract Value Type
526 null: Value can be empty
527 valid: A function returning the value if it is valid otherwise nil
528 depends: A table of option => value pairs of which one must be true
529 default: The default value
530 size: The size of the input fields
531 rmempty: Unset value if empty
532 optional: This value is optional (see AbstractSection.optionals)
534 AbstractValue = class(Node)
536 function AbstractValue.__init__(self, map, option, ...)
537 Node.__init__(self, ...)
540 self.config = map.config
541 self.tag_invalid = {}
547 self.optional = false
550 -- Add a dependencie to another section field
551 function AbstractValue.depends(self, field, value)
552 table.insert(self.deps, {field=field, value=value})
555 -- Return whether this object should be created
556 function AbstractValue.formcreated(self, section)
557 local key = "cbi.opt."..self.config.."."..section
558 return (luci.http.formvalue(key) == self.option)
561 -- Returns the formvalue for this object
562 function AbstractValue.formvalue(self, section)
563 local key = "cbid."..self.map.config.."."..section.."."..self.option
564 return luci.http.formvalue(key)
567 function AbstractValue.parse(self, section)
568 local fvalue = self:formvalue(section)
570 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
571 fvalue = self:validate(fvalue)
573 self.tag_invalid[section] = true
575 if fvalue and not (fvalue == self:cfgvalue(section)) then
576 self:write(section, fvalue)
578 else -- Unset the UCI or error
579 if self.rmempty or self.optional then
585 -- Render if this value exists or if it is mandatory
586 function AbstractValue.render(self, s, scope)
587 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
590 scope.cbid = "cbid." .. self.config ..
594 scope.ifattr = function(cond,key,val)
596 return string.format(
597 ' %s="%s"', tostring(key),
600 or (type(self[key]) ~= "function" and self[key])
608 scope.attr = function(...)
609 return scope.ifattr( true, ... )
612 Node.render(self, scope)
616 -- Return the UCI value of this object
617 function AbstractValue.cfgvalue(self, section)
618 return self.map:get(section, self.option)
621 -- Validate the form value
622 function AbstractValue.validate(self, value)
627 function AbstractValue.write(self, section, value)
628 return self.map:set(section, self.option, value)
632 function AbstractValue.remove(self, section)
633 return self.map:del(section, self.option)
640 Value - A one-line value
641 maxlength: The maximum length
643 Value = class(AbstractValue)
645 function Value.__init__(self, ...)
646 AbstractValue.__init__(self, ...)
647 self.template = "cbi/value"
652 function Value.value(self, key, val)
654 table.insert(self.keylist, tostring(key))
655 table.insert(self.vallist, tostring(val))
659 -- DummyValue - This does nothing except being there
660 DummyValue = class(AbstractValue)
662 function DummyValue.__init__(self, map, ...)
663 AbstractValue.__init__(self, map, ...)
664 self.template = "cbi/dvalue"
668 function DummyValue.parse(self)
674 Flag - A flag being enabled or disabled
676 Flag = class(AbstractValue)
678 function Flag.__init__(self, ...)
679 AbstractValue.__init__(self, ...)
680 self.template = "cbi/fvalue"
686 -- A flag can only have two states: set or unset
687 function Flag.parse(self, section)
688 local fvalue = self:formvalue(section)
691 fvalue = self.enabled
693 fvalue = self.disabled
696 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
697 if not(fvalue == self:cfgvalue(section)) then
698 self:write(section, fvalue)
708 ListValue - A one-line value predefined in a list
709 widget: The widget that will be used (select, radio)
711 ListValue = class(AbstractValue)
713 function ListValue.__init__(self, ...)
714 AbstractValue.__init__(self, ...)
715 self.template = "cbi/lvalue"
720 self.widget = "select"
723 function ListValue.value(self, key, val)
725 table.insert(self.keylist, tostring(key))
726 table.insert(self.vallist, tostring(val))
729 function ListValue.validate(self, val)
730 if luci.util.contains(self.keylist, val) then
740 MultiValue - Multiple delimited values
741 widget: The widget that will be used (select, checkbox)
742 delimiter: The delimiter that will separate the values (default: " ")
744 MultiValue = class(AbstractValue)
746 function MultiValue.__init__(self, ...)
747 AbstractValue.__init__(self, ...)
748 self.template = "cbi/mvalue"
752 self.widget = "checkbox"
756 function MultiValue.render(self, ...)
757 if self.widget == "select" and not self.size then
758 self.size = #self.vallist
761 AbstractValue.render(self, ...)
764 function MultiValue.value(self, key, val)
766 table.insert(self.keylist, tostring(key))
767 table.insert(self.vallist, tostring(val))
770 function MultiValue.valuelist(self, section)
771 local val = self:cfgvalue(section)
773 if not(type(val) == "string") then
777 return luci.util.split(val, self.delimiter)
780 function MultiValue.validate(self, val)
781 val = (type(val) == "table") and val or {val}
785 for i, value in ipairs(val) do
786 if luci.util.contains(self.keylist, value) then
787 result = result and (result .. self.delimiter .. value) or value