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 and config:gsub("[^%w]+", "") or ""
97 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
98 if option then key = key .. "_" .. tostring(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)
254 Page.__init__ = Node.__init__
255 Page.parse = function() end
259 SimpleForm - A Simple non-UCI form
261 SimpleForm = class(Node)
263 function SimpleForm.__init__(self, config, title, description, data)
264 Node.__init__(self, title, description)
266 self.data = data or {}
267 self.template = "cbi/simpleform"
271 function SimpleForm.parse(self, ...)
272 if luci.http.formvalue("cbi.submit") then
273 Node.parse(self, 1, ...)
277 for k, j in ipairs(self.children) do
278 for i, v in ipairs(j.children) do
280 and (not v.tag_missing or not v.tag_missing[1])
281 and (not v.tag_invalid or not v.tag_invalid[1])
286 not luci.http.formvalue("cbi.submit") and 0
290 self.dorender = not self.handle or self:handle(state, self.data) ~= false
293 function SimpleForm.render(self, ...)
294 if self.dorender then
295 Node.render(self, ...)
299 function SimpleForm.section(self, class, ...)
300 if instanceof(class, AbstractSection) then
301 local obj = class(self, ...)
305 error("class must be a descendent of AbstractSection")
309 -- Creates a child field
310 function SimpleForm.field(self, class, ...)
312 for k, v in ipairs(self.children) do
313 if instanceof(v, SimpleSection) then
319 section = self:section(SimpleSection)
322 if instanceof(class, AbstractValue) then
323 local obj = class(self, ...)
324 obj.track_missing = true
328 error("class must be a descendent of AbstractValue")
332 function SimpleForm.set(self, section, option, value)
333 self.data[option] = value
337 function SimpleForm.del(self, section, option)
338 self.data[option] = nil
342 function SimpleForm.get(self, section, option)
343 return self.data[option]
351 AbstractSection = class(Node)
353 function AbstractSection.__init__(self, map, sectiontype, ...)
354 Node.__init__(self, ...)
355 self.sectiontype = sectiontype
357 self.config = map.config
362 self.addremove = false
366 -- Appends a new option
367 function AbstractSection.option(self, class, option, ...)
368 if instanceof(class, AbstractValue) then
369 local obj = class(self.map, option, ...)
371 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
376 error("class must be a descendent of AbstractValue")
380 -- Parse optional options
381 function AbstractSection.parse_optionals(self, section)
382 if not self.optional then
386 self.optionals[section] = {}
388 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
389 for k,v in ipairs(self.children) do
390 if v.optional and not v:cfgvalue(section) then
391 if field == v.option then
394 table.insert(self.optionals[section], v)
399 if field and #field > 0 and self.dynamic then
400 self:add_dynamic(field)
404 -- Add a dynamic option
405 function AbstractSection.add_dynamic(self, field, optional)
406 local o = self:option(Value, field, field)
407 o.optional = optional
410 -- Parse all dynamic options
411 function AbstractSection.parse_dynamic(self, section)
412 if not self.dynamic then
416 local arr = luci.util.clone(self:cfgvalue(section))
417 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
418 for k, v in pairs(form) do
422 for key,val in pairs(arr) do
425 for i,c in ipairs(self.children) do
426 if c.option == key then
431 if create and key:sub(1, 1) ~= "." then
432 self:add_dynamic(key, true)
437 -- Returns the section's UCI table
438 function AbstractSection.cfgvalue(self, section)
439 return self.map:get(section)
442 -- Removes the section
443 function AbstractSection.remove(self, section)
444 return self.map:del(section)
447 -- Creates the section
448 function AbstractSection.create(self, section)
452 stat = self.map:set(section, nil, self.sectiontype)
454 section = self.map:add(self.sectiontype)
459 for k,v in pairs(self.children) do
461 self.map:set(section, v.option, v.default)
465 for k,v in pairs(self.defaults) do
466 self.map:set(section, k, v)
474 SimpleSection = class(AbstractSection)
476 function SimpleSection.__init__(self, form, ...)
477 AbstractSection.__init__(self, form, nil, ...)
478 self.template = "cbi/nullsection"
482 Table = class(AbstractSection)
484 function Table.__init__(self, form, data, ...)
485 local datasource = {}
486 datasource.config = "table"
489 function datasource.get(self, section, option)
490 return data[section] and data[section][option]
493 function datasource.del(...)
497 AbstractSection.__init__(self, datasource, "table", ...)
498 self.template = "cbi/tblsection"
499 self.rowcolors = true
500 self.anonymous = true
503 function Table.parse(self)
504 for i, k in ipairs(self:cfgsections()) do
505 if luci.http.formvalue("cbi.submit") then
511 function Table.cfgsections(self)
514 for i, v in luci.util.kspairs(self.data) do
515 table.insert(sections, i)
524 NamedSection - A fixed configuration section defined by its name
526 NamedSection = class(AbstractSection)
528 function NamedSection.__init__(self, map, section, type, ...)
529 AbstractSection.__init__(self, map, type, ...)
530 Node._i18n(self, map.config, section, nil, ...)
532 self.template = "cbi/nsection"
533 self.section = section
534 self.addremove = false
537 function NamedSection.parse(self)
538 local s = self.section
539 local active = self:cfgvalue(s)
542 if self.addremove then
543 local path = self.config.."."..s
544 if active then -- Remove the section
545 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
548 else -- Create and apply default values
549 if luci.http.formvalue("cbi.cns."..path) then
557 AbstractSection.parse_dynamic(self, s)
558 if luci.http.formvalue("cbi.submit") then
561 AbstractSection.parse_optionals(self, s)
567 TypedSection - A (set of) configuration section(s) defined by the type
568 addremove: Defines whether the user can add/remove sections of this type
569 anonymous: Allow creating anonymous sections
570 validate: a validation function returning nil if the section is invalid
572 TypedSection = class(AbstractSection)
574 function TypedSection.__init__(self, map, type, ...)
575 AbstractSection.__init__(self, map, type, ...)
576 Node._i18n(self, map.config, type, nil, ...)
578 self.template = "cbi/tsection"
581 self.anonymous = false
584 -- Return all matching UCI sections for this TypedSection
585 function TypedSection.cfgsections(self)
587 uci.foreach(self.map.config, self.sectiontype,
589 if self:checkscope(section[".name"]) then
590 table.insert(sections, section[".name"])
597 -- Limits scope to sections that have certain option => value pairs
598 function TypedSection.depends(self, option, value)
599 table.insert(self.deps, {option=option, value=value})
602 function TypedSection.parse(self)
603 if self.addremove then
605 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
606 local name = luci.http.formvalue(crval)
607 if self.anonymous then
613 -- Ignore if it already exists
614 if self:cfgvalue(name) then
618 name = self:checkscope(name)
621 self.err_invalid = true
624 if name and name:len() > 0 then
631 crval = REMOVE_PREFIX .. self.config
632 name = luci.http.formvaluetable(crval)
633 for k,v in pairs(name) do
634 if self:cfgvalue(k) and self:checkscope(k) then
640 for i, k in ipairs(self:cfgsections()) do
641 AbstractSection.parse_dynamic(self, k)
642 if luci.http.formvalue("cbi.submit") then
645 AbstractSection.parse_optionals(self, k)
649 -- Verifies scope of sections
650 function TypedSection.checkscope(self, section)
651 -- Check if we are not excluded
652 if self.filter and not self:filter(section) then
656 -- Check if at least one dependency is met
657 if #self.deps > 0 and self:cfgvalue(section) then
660 for k, v in ipairs(self.deps) do
661 if self:cfgvalue(section)[v.option] == v.value then
671 return self:validate(section)
675 -- Dummy validate function
676 function TypedSection.validate(self, section)
682 AbstractValue - An abstract Value Type
683 null: Value can be empty
684 valid: A function returning the value if it is valid otherwise nil
685 depends: A table of option => value pairs of which one must be true
686 default: The default value
687 size: The size of the input fields
688 rmempty: Unset value if empty
689 optional: This value is optional (see AbstractSection.optionals)
691 AbstractValue = class(Node)
693 function AbstractValue.__init__(self, map, option, ...)
694 Node.__init__(self, ...)
697 self.config = map.config
698 self.tag_invalid = {}
699 self.tag_missing = {}
703 self.track_missing = false
707 self.optional = false
708 self.stateful = false
711 -- Add a dependencie to another section field
712 function AbstractValue.depends(self, field, value)
713 table.insert(self.deps, {field=field, value=value})
716 -- Generates the unique CBID
717 function AbstractValue.cbid(self, section)
718 return "cbid."..self.map.config.."."..section.."."..self.option
721 -- Return whether this object should be created
722 function AbstractValue.formcreated(self, section)
723 local key = "cbi.opt."..self.config.."."..section
724 return (luci.http.formvalue(key) == self.option)
727 -- Returns the formvalue for this object
728 function AbstractValue.formvalue(self, section)
729 return luci.http.formvalue(self:cbid(section))
732 function AbstractValue.additional(self, value)
733 self.optional = value
736 function AbstractValue.mandatory(self, value)
737 self.rmempty = not value
740 function AbstractValue.parse(self, section)
741 local fvalue = self:formvalue(section)
742 local cvalue = self:cfgvalue(section)
744 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
745 fvalue = self:transform(self:validate(fvalue, section))
747 self.tag_invalid[section] = true
749 if fvalue and not (fvalue == cvalue) then
750 self:write(section, fvalue)
752 else -- Unset the UCI or error
753 if self.rmempty or self.optional then
755 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
756 self.tag_missing[section] = true
761 -- Render if this value exists or if it is mandatory
762 function AbstractValue.render(self, s, scope)
763 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
766 scope.cbid = self:cbid(s)
768 scope.ifattr = function(cond,key,val)
770 return string.format(
771 ' %s="%s"', tostring(key),
772 luci.util.pcdata(tostring( val
774 or (type(self[key]) ~= "function" and self[key])
782 scope.attr = function(...)
783 return scope.ifattr( true, ... )
786 Node.render(self, scope)
790 -- Return the UCI value of this object
791 function AbstractValue.cfgvalue(self, section)
793 and self.map:stateget(section, self.option)
794 or self.map:get(section, self.option)
797 -- Validate the form value
798 function AbstractValue.validate(self, value)
802 AbstractValue.transform = AbstractValue.validate
806 function AbstractValue.write(self, section, value)
807 return self.map:set(section, self.option, value)
811 function AbstractValue.remove(self, section)
812 return self.map:del(section, self.option)
819 Value - A one-line value
820 maxlength: The maximum length
822 Value = class(AbstractValue)
824 function Value.__init__(self, ...)
825 AbstractValue.__init__(self, ...)
826 self.template = "cbi/value"
831 function Value.value(self, key, val)
833 table.insert(self.keylist, tostring(key))
834 table.insert(self.vallist, tostring(val))
838 -- DummyValue - This does nothing except being there
839 DummyValue = class(AbstractValue)
841 function DummyValue.__init__(self, map, ...)
842 AbstractValue.__init__(self, map, ...)
843 self.template = "cbi/dvalue"
847 function DummyValue.parse(self)
853 Flag - A flag being enabled or disabled
855 Flag = class(AbstractValue)
857 function Flag.__init__(self, ...)
858 AbstractValue.__init__(self, ...)
859 self.template = "cbi/fvalue"
865 -- A flag can only have two states: set or unset
866 function Flag.parse(self, section)
867 local fvalue = self:formvalue(section)
870 fvalue = self.enabled
872 fvalue = self.disabled
875 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
876 if not(fvalue == self:cfgvalue(section)) then
877 self:write(section, fvalue)
887 ListValue - A one-line value predefined in a list
888 widget: The widget that will be used (select, radio)
890 ListValue = class(AbstractValue)
892 function ListValue.__init__(self, ...)
893 AbstractValue.__init__(self, ...)
894 self.template = "cbi/lvalue"
899 self.widget = "select"
902 function ListValue.value(self, key, val)
904 table.insert(self.keylist, tostring(key))
905 table.insert(self.vallist, tostring(val))
908 function ListValue.validate(self, val)
909 if luci.util.contains(self.keylist, val) then
919 MultiValue - Multiple delimited values
920 widget: The widget that will be used (select, checkbox)
921 delimiter: The delimiter that will separate the values (default: " ")
923 MultiValue = class(AbstractValue)
925 function MultiValue.__init__(self, ...)
926 AbstractValue.__init__(self, ...)
927 self.template = "cbi/mvalue"
931 self.widget = "checkbox"
935 function MultiValue.render(self, ...)
936 if self.widget == "select" and not self.size then
937 self.size = #self.vallist
940 AbstractValue.render(self, ...)
943 function MultiValue.value(self, key, val)
945 table.insert(self.keylist, tostring(key))
946 table.insert(self.vallist, tostring(val))
949 function MultiValue.valuelist(self, section)
950 local val = self:cfgvalue(section)
952 if not(type(val) == "string") then
956 return luci.util.split(val, self.delimiter)
959 function MultiValue.validate(self, val)
960 val = (type(val) == "table") and val or {val}
964 for i, value in ipairs(val) do
965 if luci.util.contains(self.keylist, value) then
966 result = result and (result .. self.delimiter .. value) or value
974 TextValue - A multi-line value
977 TextValue = class(AbstractValue)
979 function TextValue.__init__(self, ...)
980 AbstractValue.__init__(self, ...)
981 self.template = "cbi/tvalue"
987 Button = class(AbstractValue)
989 function Button.__init__(self, ...)
990 AbstractValue.__init__(self, ...)
991 self.template = "cbi/button"
992 self.inputstyle = nil