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 -- Loads a CBI map from given file, creating an environment and returns it
43 function load(cbimap, ...)
46 require("luci.config")
49 local cbidir = luci.util.libpath() .. "/model/cbi/"
50 local func, err = loadfile(cbidir..cbimap..".lua")
56 luci.i18n.loadc("cbi")
58 luci.util.resfenv(func)
59 luci.util.updfenv(func, luci.cbi)
60 luci.util.extfenv(func, "translate", luci.i18n.translate)
61 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
62 luci.util.extfenv(func, "arg", {...})
66 for i, map in ipairs(maps) do
67 if not instanceof(map, Node) then
68 error("CBI map returns no valid map object!")
76 -- Node pseudo abstract class
79 function Node.__init__(self, title, description)
81 self.title = title or ""
82 self.description = description or ""
83 self.template = "cbi/node"
87 function Node._i18n(self, config, section, option, title, description)
90 if type(luci.i18n) == "table" then
92 local key = config:gsub("[^%w]+", "")
94 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
95 if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end
97 self.title = title or luci.i18n.translate( key, option or section or config )
98 self.description = description or luci.i18n.translate( key .. "_desc", "" )
102 -- Append child nodes
103 function Node.append(self, obj)
104 table.insert(self.children, obj)
107 -- Parse this node and its children
108 function Node.parse(self, ...)
109 for k, child in ipairs(self.children) do
115 function Node.render(self, scope)
119 luci.template.render(self.template, scope)
122 -- Render the children
123 function Node.render_children(self, ...)
124 for k, node in ipairs(self.children) do
131 A simple template element
133 Template = class(Node)
135 function Template.__init__(self, template)
137 self.template = template
142 Map - A map describing a configuration file
146 function Map.__init__(self, config, ...)
147 Node.__init__(self, ...)
148 Node._i18n(self, config, nil, nil, ...)
151 self.parsechain = {self.config}
152 self.template = "cbi/map"
153 if not uci.load(self.config) then
154 error("Unable to read UCI data: " .. self.config)
159 -- Chain foreign config
160 function Map.chain(self, config)
161 table.insert(self.parsechain, config)
164 -- Use optimized UCI writing
165 function Map.parse(self, ...)
166 Node.parse(self, ...)
167 for i, config in ipairs(self.parsechain) do
170 if luci.http.formvalue("cbi.apply") then
171 for i, config in ipairs(self.parsechain) do
173 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
174 luci.util.exec(luci.config.uci_oncommit[config])
177 -- Refresh data because commit changes section names
183 Node.parse(self, ...)
186 for i, config in ipairs(self.parsechain) do
191 -- Creates a child section
192 function Map.section(self, class, ...)
193 if instanceof(class, AbstractSection) then
194 local obj = class(self, ...)
198 error("class must be a descendent of AbstractSection")
203 function Map.add(self, sectiontype)
204 return uci.add(self.config, sectiontype)
208 function Map.set(self, section, option, value)
210 return uci.set(self.config, section, option, value)
212 return uci.set(self.config, section, value)
217 function Map.del(self, section, option)
219 return uci.delete(self.config, section, option)
221 return uci.delete(self.config, section)
226 function Map.get(self, section, option)
228 return uci.get_all(self.config)
230 return uci.get(self.config, section, option)
232 return uci.get_all(self.config, section)
238 SimpleForm - A Simple non-UCI form
240 SimpleForm = class(Node)
242 function SimpleForm.__init__(self, config, title, description, data)
243 Node.__init__(self, title, description)
245 self.data = data or {}
246 self.template = "cbi/simpleform"
250 function SimpleForm.parse(self, ...)
251 Node.parse(self, 1, ...)
254 for i, v in ipairs(self.children) do
255 valid = valid and not v.tag_missing[1] and not v.tag_invalid[1]
259 not luci.http.formvalue("cbi.submit") and 0
263 self.dorender = self:handle(state)
266 function SimpleForm.render(self, ...)
267 if self.dorender then
268 Node.render(self, ...)
272 -- Creates a child section
273 function SimpleForm.field(self, class, ...)
274 if instanceof(class, AbstractValue) then
275 local obj = class(self, ...)
279 error("class must be a descendent of AbstractValue")
283 function SimpleForm.set(self, section, option, value)
284 self.data[option] = value
288 function SimpleForm.del(self, section, option)
289 self.data[option] = nil
293 function SimpleForm.get(self, section, option)
294 return self.data[option]
302 AbstractSection = class(Node)
304 function AbstractSection.__init__(self, map, sectiontype, ...)
305 Node.__init__(self, ...)
306 self.sectiontype = sectiontype
308 self.config = map.config
313 self.addremove = false
317 -- Appends a new option
318 function AbstractSection.option(self, class, option, ...)
319 if instanceof(class, AbstractValue) then
320 local obj = class(self.map, option, ...)
322 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
327 error("class must be a descendent of AbstractValue")
331 -- Parse optional options
332 function AbstractSection.parse_optionals(self, section)
333 if not self.optional then
337 self.optionals[section] = {}
339 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
340 for k,v in ipairs(self.children) do
341 if v.optional and not v:cfgvalue(section) then
342 if field == v.option then
345 table.insert(self.optionals[section], v)
350 if field and #field > 0 and self.dynamic then
351 self:add_dynamic(field)
355 -- Add a dynamic option
356 function AbstractSection.add_dynamic(self, field, optional)
357 local o = self:option(Value, field, field)
358 o.optional = optional
361 -- Parse all dynamic options
362 function AbstractSection.parse_dynamic(self, section)
363 if not self.dynamic then
367 local arr = luci.util.clone(self:cfgvalue(section))
368 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
369 for k, v in pairs(form) do
373 for key,val in pairs(arr) do
376 for i,c in ipairs(self.children) do
377 if c.option == key then
382 if create and key:sub(1, 1) ~= "." then
383 self:add_dynamic(key, true)
388 -- Returns the section's UCI table
389 function AbstractSection.cfgvalue(self, section)
390 return self.map:get(section)
393 -- Removes the section
394 function AbstractSection.remove(self, section)
395 return self.map:del(section)
398 -- Creates the section
399 function AbstractSection.create(self, section)
403 stat = self.map:set(section, nil, self.sectiontype)
405 section = self.map:add(self.sectiontype)
410 for k,v in pairs(self.children) do
412 self.map:set(section, v.option, v.default)
416 for k,v in pairs(self.defaults) do
417 self.map:set(section, k, v)
427 NamedSection - A fixed configuration section defined by its name
429 NamedSection = class(AbstractSection)
431 function NamedSection.__init__(self, map, section, type, ...)
432 AbstractSection.__init__(self, map, type, ...)
433 Node._i18n(self, map.config, section, nil, ...)
435 self.template = "cbi/nsection"
436 self.section = section
437 self.addremove = false
440 function NamedSection.parse(self)
441 local s = self.section
442 local active = self:cfgvalue(s)
445 if self.addremove then
446 local path = self.config.."."..s
447 if active then -- Remove the section
448 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
451 else -- Create and apply default values
452 if luci.http.formvalue("cbi.cns."..path) then
460 AbstractSection.parse_dynamic(self, s)
461 if luci.http.formvalue("cbi.submit") then
464 AbstractSection.parse_optionals(self, s)
470 TypedSection - A (set of) configuration section(s) defined by the type
471 addremove: Defines whether the user can add/remove sections of this type
472 anonymous: Allow creating anonymous sections
473 validate: a validation function returning nil if the section is invalid
475 TypedSection = class(AbstractSection)
477 function TypedSection.__init__(self, map, type, ...)
478 AbstractSection.__init__(self, map, type, ...)
479 Node._i18n(self, map.config, type, nil, ...)
481 self.template = "cbi/tsection"
484 self.anonymous = false
487 -- Return all matching UCI sections for this TypedSection
488 function TypedSection.cfgsections(self)
490 uci.foreach(self.map.config, self.sectiontype,
492 if self:checkscope(section[".name"]) then
493 table.insert(sections, section[".name"])
500 -- Limits scope to sections that have certain option => value pairs
501 function TypedSection.depends(self, option, value)
502 table.insert(self.deps, {option=option, value=value})
505 function TypedSection.parse(self)
506 if self.addremove then
508 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
509 local name = luci.http.formvalue(crval)
510 if self.anonymous then
516 -- Ignore if it already exists
517 if self:cfgvalue(name) then
521 name = self:checkscope(name)
524 self.err_invalid = true
527 if name and name:len() > 0 then
534 crval = "cbi.rts." .. self.config
535 name = luci.http.formvaluetable(crval)
536 for k,v in pairs(name) do
537 if self:cfgvalue(k) and self:checkscope(k) then
543 for i, k in ipairs(self:cfgsections()) do
544 AbstractSection.parse_dynamic(self, k)
545 if luci.http.formvalue("cbi.submit") then
548 AbstractSection.parse_optionals(self, k)
552 -- Verifies scope of sections
553 function TypedSection.checkscope(self, section)
554 -- Check if we are not excluded
555 if self.filter and not self:filter(section) then
559 -- Check if at least one dependency is met
560 if #self.deps > 0 and self:cfgvalue(section) then
563 for k, v in ipairs(self.deps) do
564 if self:cfgvalue(section)[v.option] == v.value then
574 return self:validate(section)
578 -- Dummy validate function
579 function TypedSection.validate(self, section)
585 AbstractValue - An abstract Value Type
586 null: Value can be empty
587 valid: A function returning the value if it is valid otherwise nil
588 depends: A table of option => value pairs of which one must be true
589 default: The default value
590 size: The size of the input fields
591 rmempty: Unset value if empty
592 optional: This value is optional (see AbstractSection.optionals)
594 AbstractValue = class(Node)
596 function AbstractValue.__init__(self, map, option, ...)
597 Node.__init__(self, ...)
600 self.config = map.config
601 self.tag_invalid = {}
602 self.tag_missing = {}
608 self.optional = false
611 -- Add a dependencie to another section field
612 function AbstractValue.depends(self, field, value)
613 table.insert(self.deps, {field=field, value=value})
616 -- Return whether this object should be created
617 function AbstractValue.formcreated(self, section)
618 local key = "cbi.opt."..self.config.."."..section
619 return (luci.http.formvalue(key) == self.option)
622 -- Returns the formvalue for this object
623 function AbstractValue.formvalue(self, section)
624 local key = "cbid."..self.map.config.."."..section.."."..self.option
625 return luci.http.formvalue(key)
628 function AbstractValue.additional(self, value)
629 self.optional = value
632 function AbstractValue.mandatory(self, value)
633 self.rmempty = not value
636 function AbstractValue.parse(self, section)
637 local fvalue = self:formvalue(section)
638 local cvalue = self:cfgvalue(section)
640 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
641 fvalue = self:transform(self:validate(fvalue))
643 self.tag_invalid[section] = true
645 if fvalue and not (fvalue == self:cfgvalue(section)) then
646 self:write(section, fvalue)
648 else -- Unset the UCI or error
649 if self.rmempty or self.optional then
651 elseif not fvalue or fvalue ~= cvalue then
652 self.tag_missing[section] = true
657 -- Render if this value exists or if it is mandatory
658 function AbstractValue.render(self, s, scope)
659 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
662 scope.cbid = "cbid." .. self.config ..
666 scope.ifattr = function(cond,key,val)
668 return string.format(
669 ' %s="%s"', tostring(key),
672 or (type(self[key]) ~= "function" and self[key])
680 scope.attr = function(...)
681 return scope.ifattr( true, ... )
684 Node.render(self, scope)
688 -- Return the UCI value of this object
689 function AbstractValue.cfgvalue(self, section)
690 return self.map:get(section, self.option)
693 -- Validate the form value
694 function AbstractValue.validate(self, value)
698 AbstractValue.transform = AbstractValue.validate
702 function AbstractValue.write(self, section, value)
703 return self.map:set(section, self.option, value)
707 function AbstractValue.remove(self, section)
708 return self.map:del(section, self.option)
715 Value - A one-line value
716 maxlength: The maximum length
718 Value = class(AbstractValue)
720 function Value.__init__(self, ...)
721 AbstractValue.__init__(self, ...)
722 self.template = "cbi/value"
727 function Value.value(self, key, val)
729 table.insert(self.keylist, tostring(key))
730 table.insert(self.vallist, tostring(val))
734 -- DummyValue - This does nothing except being there
735 DummyValue = class(AbstractValue)
737 function DummyValue.__init__(self, map, ...)
738 AbstractValue.__init__(self, map, ...)
739 self.template = "cbi/dvalue"
743 function DummyValue.parse(self)
749 Flag - A flag being enabled or disabled
751 Flag = class(AbstractValue)
753 function Flag.__init__(self, ...)
754 AbstractValue.__init__(self, ...)
755 self.template = "cbi/fvalue"
761 -- A flag can only have two states: set or unset
762 function Flag.parse(self, section)
763 local fvalue = self:formvalue(section)
766 fvalue = self.enabled
768 fvalue = self.disabled
771 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
772 if not(fvalue == self:cfgvalue(section)) then
773 self:write(section, fvalue)
783 ListValue - A one-line value predefined in a list
784 widget: The widget that will be used (select, radio)
786 ListValue = class(AbstractValue)
788 function ListValue.__init__(self, ...)
789 AbstractValue.__init__(self, ...)
790 self.template = "cbi/lvalue"
795 self.widget = "select"
798 function ListValue.value(self, key, val)
800 table.insert(self.keylist, tostring(key))
801 table.insert(self.vallist, tostring(val))
804 function ListValue.validate(self, val)
805 if luci.util.contains(self.keylist, val) then
815 MultiValue - Multiple delimited values
816 widget: The widget that will be used (select, checkbox)
817 delimiter: The delimiter that will separate the values (default: " ")
819 MultiValue = class(AbstractValue)
821 function MultiValue.__init__(self, ...)
822 AbstractValue.__init__(self, ...)
823 self.template = "cbi/mvalue"
827 self.widget = "checkbox"
831 function MultiValue.render(self, ...)
832 if self.widget == "select" and not self.size then
833 self.size = #self.vallist
836 AbstractValue.render(self, ...)
839 function MultiValue.value(self, key, val)
841 table.insert(self.keylist, tostring(key))
842 table.insert(self.vallist, tostring(val))
845 function MultiValue.valuelist(self, section)
846 local val = self:cfgvalue(section)
848 if not(type(val) == "string") then
852 return luci.util.split(val, self.delimiter)
855 function MultiValue.validate(self, val)
856 val = (type(val) == "table") and val or {val}
860 for i, value in ipairs(val) do
861 if luci.util.contains(self.keylist, value) then
862 result = result and (result .. self.delimiter .. value) or value