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 if luci.http.formvalue("cbi.submit") then
264 Node.parse(self, 1, ...)
268 for i, v in ipairs(self.children) do
270 and (not v.tag_missing or not v.tag_missing[1])
271 and (not v.tag_invalid or not v.tag_invalid[1])
275 not luci.http.formvalue("cbi.submit") and 0
279 self.dorender = self:handle(state, self.data)
282 function SimpleForm.render(self, ...)
283 if self.dorender then
284 Node.render(self, ...)
288 -- Creates a child section
289 function SimpleForm.field(self, class, ...)
290 if instanceof(class, AbstractValue) then
291 local obj = class(self, ...)
292 obj.track_missing = true
296 error("class must be a descendent of AbstractValue")
300 function SimpleForm.set(self, section, option, value)
301 self.data[option] = value
305 function SimpleForm.del(self, section, option)
306 self.data[option] = nil
310 function SimpleForm.get(self, section, option)
311 return self.data[option]
319 AbstractSection = class(Node)
321 function AbstractSection.__init__(self, map, sectiontype, ...)
322 Node.__init__(self, ...)
323 self.sectiontype = sectiontype
325 self.config = map.config
330 self.addremove = false
334 -- Appends a new option
335 function AbstractSection.option(self, class, option, ...)
336 if instanceof(class, AbstractValue) then
337 local obj = class(self.map, option, ...)
339 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
344 error("class must be a descendent of AbstractValue")
348 -- Parse optional options
349 function AbstractSection.parse_optionals(self, section)
350 if not self.optional then
354 self.optionals[section] = {}
356 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
357 for k,v in ipairs(self.children) do
358 if v.optional and not v:cfgvalue(section) then
359 if field == v.option then
362 table.insert(self.optionals[section], v)
367 if field and #field > 0 and self.dynamic then
368 self:add_dynamic(field)
372 -- Add a dynamic option
373 function AbstractSection.add_dynamic(self, field, optional)
374 local o = self:option(Value, field, field)
375 o.optional = optional
378 -- Parse all dynamic options
379 function AbstractSection.parse_dynamic(self, section)
380 if not self.dynamic then
384 local arr = luci.util.clone(self:cfgvalue(section))
385 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
386 for k, v in pairs(form) do
390 for key,val in pairs(arr) do
393 for i,c in ipairs(self.children) do
394 if c.option == key then
399 if create and key:sub(1, 1) ~= "." then
400 self:add_dynamic(key, true)
405 -- Returns the section's UCI table
406 function AbstractSection.cfgvalue(self, section)
407 return self.map:get(section)
410 -- Removes the section
411 function AbstractSection.remove(self, section)
412 return self.map:del(section)
415 -- Creates the section
416 function AbstractSection.create(self, section)
420 stat = self.map:set(section, nil, self.sectiontype)
422 section = self.map:add(self.sectiontype)
427 for k,v in pairs(self.children) do
429 self.map:set(section, v.option, v.default)
433 for k,v in pairs(self.defaults) do
434 self.map:set(section, k, v)
444 NamedSection - A fixed configuration section defined by its name
446 NamedSection = class(AbstractSection)
448 function NamedSection.__init__(self, map, section, type, ...)
449 AbstractSection.__init__(self, map, type, ...)
450 Node._i18n(self, map.config, section, nil, ...)
452 self.template = "cbi/nsection"
453 self.section = section
454 self.addremove = false
457 function NamedSection.parse(self)
458 local s = self.section
459 local active = self:cfgvalue(s)
462 if self.addremove then
463 local path = self.config.."."..s
464 if active then -- Remove the section
465 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
468 else -- Create and apply default values
469 if luci.http.formvalue("cbi.cns."..path) then
477 AbstractSection.parse_dynamic(self, s)
478 if luci.http.formvalue("cbi.submit") then
481 AbstractSection.parse_optionals(self, s)
487 TypedSection - A (set of) configuration section(s) defined by the type
488 addremove: Defines whether the user can add/remove sections of this type
489 anonymous: Allow creating anonymous sections
490 validate: a validation function returning nil if the section is invalid
492 TypedSection = class(AbstractSection)
494 function TypedSection.__init__(self, map, type, ...)
495 AbstractSection.__init__(self, map, type, ...)
496 Node._i18n(self, map.config, type, nil, ...)
498 self.template = "cbi/tsection"
501 self.anonymous = false
504 -- Return all matching UCI sections for this TypedSection
505 function TypedSection.cfgsections(self)
507 uci.foreach(self.map.config, self.sectiontype,
509 if self:checkscope(section[".name"]) then
510 table.insert(sections, section[".name"])
517 -- Limits scope to sections that have certain option => value pairs
518 function TypedSection.depends(self, option, value)
519 table.insert(self.deps, {option=option, value=value})
522 function TypedSection.parse(self)
523 if self.addremove then
525 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
526 local name = luci.http.formvalue(crval)
527 if self.anonymous then
533 -- Ignore if it already exists
534 if self:cfgvalue(name) then
538 name = self:checkscope(name)
541 self.err_invalid = true
544 if name and name:len() > 0 then
551 crval = REMOVE_PREFIX .. self.config
552 name = luci.http.formvaluetable(crval)
553 for k,v in pairs(name) do
554 if self:cfgvalue(k) and self:checkscope(k) then
560 for i, k in ipairs(self:cfgsections()) do
561 AbstractSection.parse_dynamic(self, k)
562 if luci.http.formvalue("cbi.submit") then
565 AbstractSection.parse_optionals(self, k)
569 -- Verifies scope of sections
570 function TypedSection.checkscope(self, section)
571 -- Check if we are not excluded
572 if self.filter and not self:filter(section) then
576 -- Check if at least one dependency is met
577 if #self.deps > 0 and self:cfgvalue(section) then
580 for k, v in ipairs(self.deps) do
581 if self:cfgvalue(section)[v.option] == v.value then
591 return self:validate(section)
595 -- Dummy validate function
596 function TypedSection.validate(self, section)
602 AbstractValue - An abstract Value Type
603 null: Value can be empty
604 valid: A function returning the value if it is valid otherwise nil
605 depends: A table of option => value pairs of which one must be true
606 default: The default value
607 size: The size of the input fields
608 rmempty: Unset value if empty
609 optional: This value is optional (see AbstractSection.optionals)
611 AbstractValue = class(Node)
613 function AbstractValue.__init__(self, map, option, ...)
614 Node.__init__(self, ...)
617 self.config = map.config
618 self.tag_invalid = {}
619 self.tag_missing = {}
622 self.track_missing = false
626 self.optional = false
627 self.stateful = false
630 -- Add a dependencie to another section field
631 function AbstractValue.depends(self, field, value)
632 table.insert(self.deps, {field=field, value=value})
635 -- Return whether this object should be created
636 function AbstractValue.formcreated(self, section)
637 local key = "cbi.opt."..self.config.."."..section
638 return (luci.http.formvalue(key) == self.option)
641 -- Returns the formvalue for this object
642 function AbstractValue.formvalue(self, section)
643 local key = "cbid."..self.map.config.."."..section.."."..self.option
644 return luci.http.formvalue(key)
647 function AbstractValue.additional(self, value)
648 self.optional = value
651 function AbstractValue.mandatory(self, value)
652 self.rmempty = not value
655 function AbstractValue.parse(self, section)
656 local fvalue = self:formvalue(section)
657 local cvalue = self:cfgvalue(section)
659 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
660 fvalue = self:transform(self:validate(fvalue, section))
662 self.tag_invalid[section] = true
664 if fvalue and not (fvalue == cvalue) then
665 self:write(section, fvalue)
667 else -- Unset the UCI or error
668 if self.rmempty or self.optional then
670 elseif self.track_missing and not fvalue or fvalue ~= cvalue then
671 self.tag_missing[section] = true
676 -- Render if this value exists or if it is mandatory
677 function AbstractValue.render(self, s, scope)
678 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
681 scope.cbid = "cbid." .. self.config ..
685 scope.ifattr = function(cond,key,val)
687 return string.format(
688 ' %s="%s"', tostring(key),
691 or (type(self[key]) ~= "function" and self[key])
699 scope.attr = function(...)
700 return scope.ifattr( true, ... )
703 Node.render(self, scope)
707 -- Return the UCI value of this object
708 function AbstractValue.cfgvalue(self, section)
710 and self.map:stateget(section, self.option)
711 or self.map:get(section, self.option)
714 -- Validate the form value
715 function AbstractValue.validate(self, value)
719 AbstractValue.transform = AbstractValue.validate
723 function AbstractValue.write(self, section, value)
724 return self.map:set(section, self.option, value)
728 function AbstractValue.remove(self, section)
729 return self.map:del(section, self.option)
736 Value - A one-line value
737 maxlength: The maximum length
739 Value = class(AbstractValue)
741 function Value.__init__(self, ...)
742 AbstractValue.__init__(self, ...)
743 self.template = "cbi/value"
748 function Value.value(self, key, val)
750 table.insert(self.keylist, tostring(key))
751 table.insert(self.vallist, tostring(val))
755 -- DummyValue - This does nothing except being there
756 DummyValue = class(AbstractValue)
758 function DummyValue.__init__(self, map, ...)
759 AbstractValue.__init__(self, map, ...)
760 self.template = "cbi/dvalue"
764 function DummyValue.parse(self)
770 Flag - A flag being enabled or disabled
772 Flag = class(AbstractValue)
774 function Flag.__init__(self, ...)
775 AbstractValue.__init__(self, ...)
776 self.template = "cbi/fvalue"
782 -- A flag can only have two states: set or unset
783 function Flag.parse(self, section)
784 local fvalue = self:formvalue(section)
787 fvalue = self.enabled
789 fvalue = self.disabled
792 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
793 if not(fvalue == self:cfgvalue(section)) then
794 self:write(section, fvalue)
804 ListValue - A one-line value predefined in a list
805 widget: The widget that will be used (select, radio)
807 ListValue = class(AbstractValue)
809 function ListValue.__init__(self, ...)
810 AbstractValue.__init__(self, ...)
811 self.template = "cbi/lvalue"
816 self.widget = "select"
819 function ListValue.value(self, key, val)
821 table.insert(self.keylist, tostring(key))
822 table.insert(self.vallist, tostring(val))
825 function ListValue.validate(self, val)
826 if luci.util.contains(self.keylist, val) then
836 MultiValue - Multiple delimited values
837 widget: The widget that will be used (select, checkbox)
838 delimiter: The delimiter that will separate the values (default: " ")
840 MultiValue = class(AbstractValue)
842 function MultiValue.__init__(self, ...)
843 AbstractValue.__init__(self, ...)
844 self.template = "cbi/mvalue"
848 self.widget = "checkbox"
852 function MultiValue.render(self, ...)
853 if self.widget == "select" and not self.size then
854 self.size = #self.vallist
857 AbstractValue.render(self, ...)
860 function MultiValue.value(self, key, val)
862 table.insert(self.keylist, tostring(key))
863 table.insert(self.vallist, tostring(val))
866 function MultiValue.valuelist(self, section)
867 local val = self:cfgvalue(section)
869 if not(type(val) == "string") then
873 return luci.util.split(val, self.delimiter)
876 function MultiValue.validate(self, val)
877 val = (type(val) == "table") and val or {val}
881 for i, value in ipairs(val) do
882 if luci.util.contains(self.keylist, value) then
883 result = result and (result .. self.delimiter .. value) or value
891 TextValue - A multi-line value
894 TextValue = class(AbstractValue)
896 function TextValue.__init__(self, ...)
897 AbstractValue.__init__(self, ...)
898 self.template = "cbi/tvalue"