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.sys.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 if not instanceof(map, Map) then
63 error("CBI map returns no valid map object!")
70 -- Node pseudo abstract class
73 function Node.__init__(self, title, description)
75 self.title = title or ""
76 self.description = description or ""
77 self.template = "cbi/node"
81 function Node._i18n(self, config, section, option, title, description)
84 if type(luci.i18n) == "table" then
86 local key = config:gsub("[^%w]+", "")
88 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
89 if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end
91 self.title = title or luci.i18n.translate( key, option or section or config )
92 self.description = description or luci.i18n.translate( key .. "_desc", "" )
97 function Node.append(self, obj)
98 table.insert(self.children, obj)
101 -- Parse this node and its children
102 function Node.parse(self, ...)
103 for k, child in ipairs(self.children) do
109 function Node.render(self, scope)
113 luci.template.render(self.template, scope)
116 -- Render the children
117 function Node.render_children(self, ...)
118 for k, node in ipairs(self.children) do
125 A simple template element
127 Template = class(Node)
129 function Template.__init__(self, template)
131 self.template = template
136 Map - A map describing a configuration file
140 function Map.__init__(self, config, ...)
141 Node.__init__(self, ...)
142 Node._i18n(self, config, nil, nil, ...)
145 self.template = "cbi/map"
146 if not uci.load(self.config) then
147 error("Unable to read UCI data: " .. self.config)
151 -- Use optimized UCI writing
152 function Map.parse(self, ...)
153 Node.parse(self, ...)
154 uci.save(self.config)
155 uci.unload(self.config)
158 -- Creates a child section
159 function Map.section(self, class, ...)
160 if instanceof(class, AbstractSection) then
161 local obj = class(self, ...)
165 error("class must be a descendent of AbstractSection")
170 function Map.add(self, sectiontype)
171 return uci.add(self.config, sectiontype)
175 function Map.set(self, section, option, value)
177 return uci.set(self.config, section, option, value)
179 return uci.set(self.config, section, value)
184 function Map.del(self, section, option)
186 return uci.delete(self.config, section, option)
188 return uci.delete(self.config, section)
193 function Map.get(self, section, option)
195 return uci.get_all(self.config)
197 return uci.get(self.config, section, option)
199 return uci.get_all(self.config, section)
207 AbstractSection = class(Node)
209 function AbstractSection.__init__(self, map, sectiontype, ...)
210 Node.__init__(self, ...)
211 self.sectiontype = sectiontype
213 self.config = map.config
217 self.addremove = false
221 -- Appends a new option
222 function AbstractSection.option(self, class, option, ...)
223 if instanceof(class, AbstractValue) then
224 local obj = class(self.map, option, ...)
226 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
231 error("class must be a descendent of AbstractValue")
235 -- Parse optional options
236 function AbstractSection.parse_optionals(self, section)
237 if not self.optional then
241 self.optionals[section] = {}
243 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
244 for k,v in ipairs(self.children) do
245 if v.optional and not v:cfgvalue(section) then
246 if field == v.option then
249 table.insert(self.optionals[section], v)
254 if field and #field > 0 and self.dynamic then
255 self:add_dynamic(field)
259 -- Add a dynamic option
260 function AbstractSection.add_dynamic(self, field, optional)
261 local o = self:option(Value, field, field)
262 o.optional = optional
265 -- Parse all dynamic options
266 function AbstractSection.parse_dynamic(self, section)
267 if not self.dynamic then
271 local arr = luci.util.clone(self:cfgvalue(section))
272 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
273 for k, v in pairs(form) do
277 for key,val in pairs(arr) do
280 for i,c in ipairs(self.children) do
281 if c.option == key then
286 if create and key:sub(1, 1) ~= "." then
287 self:add_dynamic(key, true)
292 -- Returns the section's UCI table
293 function AbstractSection.cfgvalue(self, section)
294 return self.map:get(section)
297 -- Removes the section
298 function AbstractSection.remove(self, section)
299 return self.map:del(section)
302 -- Creates the section
303 function AbstractSection.create(self, section)
304 return self.map:set(section, nil, self.sectiontype)
310 NamedSection - A fixed configuration section defined by its name
312 NamedSection = class(AbstractSection)
314 function NamedSection.__init__(self, map, section, type, ...)
315 AbstractSection.__init__(self, map, type, ...)
316 Node._i18n(self, map.config, section, nil, ...)
318 self.template = "cbi/nsection"
319 self.section = section
320 self.addremove = false
323 function NamedSection.parse(self)
324 local s = self.section
325 local active = self:cfgvalue(s)
328 if self.addremove then
329 local path = self.config.."."..s
330 if active then -- Remove the section
331 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
334 else -- Create and apply default values
335 if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
336 for k,v in pairs(self.children) do
337 v:write(s, v.default)
344 AbstractSection.parse_dynamic(self, s)
345 if luci.http.formvalue("cbi.submit") then
348 AbstractSection.parse_optionals(self, s)
354 TypedSection - A (set of) configuration section(s) defined by the type
355 addremove: Defines whether the user can add/remove sections of this type
356 anonymous: Allow creating anonymous sections
357 validate: a validation function returning nil if the section is invalid
359 TypedSection = class(AbstractSection)
361 function TypedSection.__init__(self, map, type, ...)
362 AbstractSection.__init__(self, map, type, ...)
363 Node._i18n(self, map.config, type, nil, ...)
365 self.template = "cbi/tsection"
369 self.anonymous = false
372 -- Return all matching UCI sections for this TypedSection
373 function TypedSection.cfgsections(self)
375 uci.foreach(self.map.config, self.sectiontype,
377 if self:checkscope(section[".name"]) then
378 table.insert(sections, section[".name"])
385 -- Creates a new section of this type with the given name (or anonymous)
386 function TypedSection.create(self, name)
388 self.map:set(name, nil, self.sectiontype)
390 name = self.map:add(self.sectiontype)
393 for k,v in pairs(self.children) do
395 self.map:set(name, v.option, v.default)
400 -- Limits scope to sections that have certain option => value pairs
401 function TypedSection.depends(self, option, value)
402 table.insert(self.deps, {option=option, value=value})
405 -- Excludes several sections by name
406 function TypedSection.exclude(self, field)
407 self.excludes[field] = true
410 function TypedSection.parse(self)
411 if self.addremove then
413 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
414 local name = luci.http.formvalue(crval)
415 if self.anonymous then
421 -- Ignore if it already exists
422 if self:cfgvalue(name) then
426 name = self:checkscope(name)
429 self.err_invalid = true
432 if name and name:len() > 0 then
439 crval = "cbi.rts." .. self.config
440 name = luci.http.formvaluetable(crval)
441 for k,v in pairs(name) do
442 if self:cfgvalue(k) and self:checkscope(k) then
448 for i, k in ipairs(self:cfgsections()) do
449 AbstractSection.parse_dynamic(self, k)
450 if luci.http.formvalue("cbi.submit") then
453 AbstractSection.parse_optionals(self, k)
457 -- Verifies scope of sections
458 function TypedSection.checkscope(self, section)
459 -- Check if we are not excluded
460 if self.excludes[section] then
464 -- Check if at least one dependency is met
465 if #self.deps > 0 and self:cfgvalue(section) then
468 for k, v in ipairs(self.deps) do
469 if self:cfgvalue(section)[v.option] == v.value then
479 return self:validate(section)
483 -- Dummy validate function
484 function TypedSection.validate(self, section)
490 AbstractValue - An abstract Value Type
491 null: Value can be empty
492 valid: A function returning the value if it is valid otherwise nil
493 depends: A table of option => value pairs of which one must be true
494 default: The default value
495 size: The size of the input fields
496 rmempty: Unset value if empty
497 optional: This value is optional (see AbstractSection.optionals)
499 AbstractValue = class(Node)
501 function AbstractValue.__init__(self, map, option, ...)
502 Node.__init__(self, ...)
505 self.config = map.config
506 self.tag_invalid = {}
512 self.optional = false
515 -- Add a dependencie to another section field
516 function AbstractValue.depends(self, field, value)
517 table.insert(self.deps, {field=field, value=value})
520 -- Return whether this object should be created
521 function AbstractValue.formcreated(self, section)
522 local key = "cbi.opt."..self.config.."."..section
523 return (luci.http.formvalue(key) == self.option)
526 -- Returns the formvalue for this object
527 function AbstractValue.formvalue(self, section)
528 local key = "cbid."..self.map.config.."."..section.."."..self.option
529 return luci.http.formvalue(key)
532 function AbstractValue.parse(self, section)
533 local fvalue = self:formvalue(section)
535 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
536 fvalue = self:validate(fvalue)
538 self.tag_invalid[section] = true
540 if fvalue and not (fvalue == self:cfgvalue(section)) then
541 self:write(section, fvalue)
543 else -- Unset the UCI or error
544 if self.rmempty or self.optional then
550 -- Render if this value exists or if it is mandatory
551 function AbstractValue.render(self, s, scope)
552 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
556 Node.render(self, scope)
560 -- Return the UCI value of this object
561 function AbstractValue.cfgvalue(self, section)
562 return self.map:get(section, self.option)
565 -- Validate the form value
566 function AbstractValue.validate(self, value)
571 function AbstractValue.write(self, section, value)
572 return self.map:set(section, self.option, value)
576 function AbstractValue.remove(self, section)
577 return self.map:del(section, self.option)
584 Value - A one-line value
585 maxlength: The maximum length
587 Value = class(AbstractValue)
589 function Value.__init__(self, ...)
590 AbstractValue.__init__(self, ...)
591 self.template = "cbi/value"
596 -- This validation is a bit more complex
597 function Value.validate(self, val)
598 if self.maxlength and tostring(val):len() > self.maxlength then
606 -- DummyValue - This does nothing except being there
607 DummyValue = class(AbstractValue)
609 function DummyValue.__init__(self, map, ...)
610 AbstractValue.__init__(self, map, ...)
611 self.template = "cbi/dvalue"
615 function DummyValue.parse(self)
619 function DummyValue.render(self, s)
620 luci.template.render(self.template, {self=self, section=s})
625 Flag - A flag being enabled or disabled
627 Flag = class(AbstractValue)
629 function Flag.__init__(self, ...)
630 AbstractValue.__init__(self, ...)
631 self.template = "cbi/fvalue"
637 -- A flag can only have two states: set or unset
638 function Flag.parse(self, section)
639 local fvalue = self:formvalue(section)
642 fvalue = self.enabled
644 fvalue = self.disabled
647 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
648 if not(fvalue == self:cfgvalue(section)) then
649 self:write(section, fvalue)
659 ListValue - A one-line value predefined in a list
660 widget: The widget that will be used (select, radio)
662 ListValue = class(AbstractValue)
664 function ListValue.__init__(self, ...)
665 AbstractValue.__init__(self, ...)
666 self.template = "cbi/lvalue"
671 self.widget = "select"
674 function ListValue.value(self, key, val)
676 table.insert(self.keylist, tostring(key))
677 table.insert(self.vallist, tostring(val))
680 function ListValue.validate(self, val)
681 if luci.util.contains(self.keylist, val) then
691 MultiValue - Multiple delimited values
692 widget: The widget that will be used (select, checkbox)
693 delimiter: The delimiter that will separate the values (default: " ")
695 MultiValue = class(AbstractValue)
697 function MultiValue.__init__(self, ...)
698 AbstractValue.__init__(self, ...)
699 self.template = "cbi/mvalue"
703 self.widget = "checkbox"
707 function MultiValue.render(self, ...)
708 if self.widget == "select" and not self.size then
709 self.size = #self.vallist
712 AbstractValue.render(self, ...)
715 function MultiValue.value(self, key, val)
717 table.insert(self.keylist, tostring(key))
718 table.insert(self.vallist, tostring(val))
721 function MultiValue.valuelist(self, section)
722 local val = self:cfgvalue(section)
724 if not(type(val) == "string") then
728 return luci.util.split(val, self.delimiter)
731 function MultiValue.validate(self, val)
732 val = (type(val) == "table") and val or {val}
736 for i, value in ipairs(val) do
737 if luci.util.contains(self.keylist, value) then
738 result = result and (result .. self.delimiter .. value) or value