2 LuCI - Configuration Bind Interface
5 Offers an interface for binding confiugration 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 class = luci.util.class
35 local instanceof = luci.util.instanceof
37 -- Loads a CBI map from given file, creating an environment and returns it
41 require("luci.config")
44 local cbidir = luci.sys.libpath() .. "/model/cbi/"
45 local func, err = loadfile(cbidir..cbimap..".lua")
51 luci.i18n.loadc("cbi")
53 luci.util.resfenv(func)
54 luci.util.updfenv(func, luci.cbi)
55 luci.util.extfenv(func, "translate", luci.i18n.translate)
56 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
60 if not instanceof(map, Map) then
61 error("CBI map returns no valid map object!")
68 -- Node pseudo abstract class
71 function Node.__init__(self, title, description)
73 self.title = title or ""
74 self.description = description or ""
75 self.template = "cbi/node"
79 function Node.append(self, obj)
80 table.insert(self.children, obj)
83 -- Parse this node and its children
84 function Node.parse(self, ...)
85 for k, child in ipairs(self.children) do
91 function Node.render(self, scope)
95 luci.template.render(self.template, scope)
98 -- Render the children
99 function Node.render_children(self, ...)
100 for k, node in ipairs(self.children) do
107 A simple template element
109 Template = class(Node)
111 function Template.__init__(self, template)
113 self.template = template
118 Map - A map describing a configuration file
122 function Map.__init__(self, config, ...)
123 Node.__init__(self, ...)
125 self.template = "cbi/map"
126 self.uci = luci.model.uci.Session()
127 self.ucidata, self.uciorder = self.uci:sections(self.config)
128 if not self.ucidata or not self.uciorder then
129 error("Unable to read UCI data: " .. self.config)
133 -- Use optimized UCI writing
134 function Map.parse(self, ...)
135 self.uci:t_load(self.config)
136 Node.parse(self, ...)
137 self.uci:t_save(self.config)
140 -- Creates a child section
141 function Map.section(self, class, ...)
142 if instanceof(class, AbstractSection) then
143 local obj = class(self, ...)
147 error("class must be a descendent of AbstractSection")
152 function Map.add(self, sectiontype)
153 local name = self.uci:t_add(self.config, sectiontype)
155 self.ucidata[name] = {}
156 self.ucidata[name][".type"] = sectiontype
157 table.insert(self.uciorder, name)
163 function Map.set(self, section, option, value)
164 local stat = self.uci:t_set(self.config, section, option, value)
166 local val = self.uci:t_get(self.config, section, option)
168 self.ucidata[section][option] = val
170 if not self.ucidata[section] then
171 self.ucidata[section] = {}
173 self.ucidata[section][".type"] = val
174 table.insert(self.uciorder, section)
181 function Map.del(self, section, option)
182 local stat = self.uci:t_del(self.config, section, option)
185 self.ucidata[section][option] = nil
187 self.ucidata[section] = nil
188 for i, k in ipairs(self.uciorder) do
190 table.remove(self.uciorder, i)
199 function Map.get(self, section, option)
201 return self.ucidata, self.uciorder
202 elseif option and self.ucidata[section] then
203 return self.ucidata[section][option]
205 return self.ucidata[section]
213 AbstractSection = class(Node)
215 function AbstractSection.__init__(self, map, sectiontype, ...)
216 Node.__init__(self, ...)
217 self.sectiontype = sectiontype
219 self.config = map.config
223 self.addremove = false
227 -- Appends a new option
228 function AbstractSection.option(self, class, ...)
229 if instanceof(class, AbstractValue) then
230 local obj = class(self.map, ...)
234 error("class must be a descendent of AbstractValue")
238 -- Parse optional options
239 function AbstractSection.parse_optionals(self, section)
240 if not self.optional then
244 self.optionals[section] = {}
246 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
247 for k,v in ipairs(self.children) do
248 if v.optional and not v:cfgvalue(section) then
249 if field == v.option then
252 table.insert(self.optionals[section], v)
257 if field and #field > 0 and self.dynamic then
258 self:add_dynamic(field)
262 -- Add a dynamic option
263 function AbstractSection.add_dynamic(self, field, optional)
264 local o = self:option(Value, field, field)
265 o.optional = optional
268 -- Parse all dynamic options
269 function AbstractSection.parse_dynamic(self, section)
270 if not self.dynamic then
274 local arr = luci.util.clone(self:cfgvalue(section))
275 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
276 for k, v in pairs(form) do
280 for key,val in pairs(arr) do
283 for i,c in ipairs(self.children) do
284 if c.option == key then
289 if create and key:sub(1, 1) ~= "." then
290 self:add_dynamic(key, true)
295 -- Returns the section's UCI table
296 function AbstractSection.cfgvalue(self, section)
297 return self.map:get(section)
300 -- Removes the section
301 function AbstractSection.remove(self, section)
302 return self.map:del(section)
305 -- Creates the section
306 function AbstractSection.create(self, section)
307 return self.map:set(section, nil, self.sectiontype)
313 NamedSection - A fixed configuration section defined by its name
315 NamedSection = class(AbstractSection)
317 function NamedSection.__init__(self, map, section, ...)
318 AbstractSection.__init__(self, map, ...)
319 self.template = "cbi/nsection"
321 self.section = section
322 self.addremove = false
325 function NamedSection.parse(self)
326 local s = self.section
327 local active = self:cfgvalue(s)
330 if self.addremove then
331 local path = self.config.."."..s
332 if active then -- Remove the section
333 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
336 else -- Create and apply default values
337 if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
338 for k,v in pairs(self.children) do
339 v:write(s, v.default)
346 AbstractSection.parse_dynamic(self, s)
347 if luci.http.formvalue("cbi.submit") then
350 AbstractSection.parse_optionals(self, s)
356 TypedSection - A (set of) configuration section(s) defined by the type
357 addremove: Defines whether the user can add/remove sections of this type
358 anonymous: Allow creating anonymous sections
359 validate: a validation function returning nil if the section is invalid
361 TypedSection = class(AbstractSection)
363 function TypedSection.__init__(self, ...)
364 AbstractSection.__init__(self, ...)
365 self.template = "cbi/tsection"
369 self.anonymous = false
372 -- Return all matching UCI sections for this TypedSection
373 function TypedSection.cfgsections(self)
375 local map, order = self.map:get()
377 for i, k in ipairs(order) do
378 if map[k][".type"] == self.sectiontype then
379 if self:checkscope(k) then
380 table.insert(sections, k)
388 -- Creates a new section of this type with the given name (or anonymous)
389 function TypedSection.create(self, name)
391 self.map:set(name, nil, self.sectiontype)
393 name = self.map:add(self.sectiontype)
396 for k,v in pairs(self.children) do
398 self.map:set(name, v.option, v.default)
403 -- Limits scope to sections that have certain option => value pairs
404 function TypedSection.depends(self, option, value)
405 table.insert(self.deps, {option=option, value=value})
408 -- Excludes several sections by name
409 function TypedSection.exclude(self, field)
410 self.excludes[field] = true
413 function TypedSection.parse(self)
414 if self.addremove then
416 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
417 local name = luci.http.formvalue(crval)
418 if self.anonymous then
424 -- Ignore if it already exists
425 if self:cfgvalue(name) then
429 name = self:checkscope(name)
432 self.err_invalid = true
435 if name and name:len() > 0 then
442 crval = "cbi.rts." .. self.config
443 name = luci.http.formvaluetable(crval)
444 for k,v in pairs(name) do
445 if self:cfgvalue(k) and self:checkscope(k) then
451 for i, k in ipairs(self:cfgsections()) do
452 AbstractSection.parse_dynamic(self, k)
453 if luci.http.formvalue("cbi.submit") then
456 AbstractSection.parse_optionals(self, k)
460 -- Verifies scope of sections
461 function TypedSection.checkscope(self, section)
462 -- Check if we are not excluded
463 if self.excludes[section] then
467 -- Check if at least one dependency is met
468 if #self.deps > 0 and self:cfgvalue(section) then
471 for k, v in ipairs(self.deps) do
472 if self:cfgvalue(section)[v.option] == v.value then
482 return self:validate(section)
486 -- Dummy validate function
487 function TypedSection.validate(self, section)
493 AbstractValue - An abstract Value Type
494 null: Value can be empty
495 valid: A function returning the value if it is valid otherwise nil
496 depends: A table of option => value pairs of which one must be true
497 default: The default value
498 size: The size of the input fields
499 rmempty: Unset value if empty
500 optional: This value is optional (see AbstractSection.optionals)
502 AbstractValue = class(Node)
504 function AbstractValue.__init__(self, map, option, ...)
505 Node.__init__(self, ...)
508 self.config = map.config
509 self.tag_invalid = {}
515 self.optional = false
518 -- Add a dependencie to another section field
519 function AbstractValue.depends(self, field, value)
520 table.insert(self.deps, {field=field, value=value})
523 -- Return whether this object should be created
524 function AbstractValue.formcreated(self, section)
525 local key = "cbi.opt."..self.config.."."..section
526 return (luci.http.formvalue(key) == self.option)
529 -- Returns the formvalue for this object
530 function AbstractValue.formvalue(self, section)
531 local key = "cbid."..self.map.config.."."..section.."."..self.option
532 return luci.http.formvalue(key)
535 function AbstractValue.parse(self, section)
536 local fvalue = self:formvalue(section)
538 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
539 fvalue = self:validate(fvalue)
541 self.tag_invalid[section] = true
543 if fvalue and not (fvalue == self:cfgvalue(section)) then
544 self:write(section, fvalue)
546 else -- Unset the UCI or error
547 if self.rmempty or self.optional then
553 -- Render if this value exists or if it is mandatory
554 function AbstractValue.render(self, s, scope)
555 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
558 Node.render(self, scope)
562 -- Return the UCI value of this object
563 function AbstractValue.cfgvalue(self, section)
564 return self.map:get(section, self.option)
567 -- Validate the form value
568 function AbstractValue.validate(self, value)
573 function AbstractValue.write(self, section, value)
574 return self.map:set(section, self.option, value)
578 function AbstractValue.remove(self, section)
579 return self.map:del(section, self.option)
586 Value - A one-line value
587 maxlength: The maximum length
588 isnumber: The value must be a valid (floating point) number
589 isinteger: The value must be a valid integer
590 ispositive: The value must be positive (and a number)
592 Value = class(AbstractValue)
594 function Value.__init__(self, ...)
595 AbstractValue.__init__(self, ...)
596 self.template = "cbi/value"
599 self.isnumber = false
600 self.isinteger = false
603 -- This validation is a bit more complex
604 function Value.validate(self, val)
605 if self.maxlength and tostring(val):len() > self.maxlength then
609 return luci.util.validate(val, self.isnumber, self.isinteger)
613 -- DummyValue - This does nothing except being there
614 DummyValue = class(AbstractValue)
616 function DummyValue.__init__(self, map, ...)
617 AbstractValue.__init__(self, map, ...)
618 self.template = "cbi/dvalue"
622 function DummyValue.parse(self)
626 function DummyValue.render(self, s)
627 luci.template.render(self.template, {self=self, section=s})
632 Flag - A flag being enabled or disabled
634 Flag = class(AbstractValue)
636 function Flag.__init__(self, ...)
637 AbstractValue.__init__(self, ...)
638 self.template = "cbi/fvalue"
644 -- A flag can only have two states: set or unset
645 function Flag.parse(self, section)
646 local fvalue = self:formvalue(section)
649 fvalue = self.enabled
651 fvalue = self.disabled
654 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
655 if not(fvalue == self:cfgvalue(section)) then
656 self:write(section, fvalue)
666 ListValue - A one-line value predefined in a list
667 widget: The widget that will be used (select, radio)
669 ListValue = class(AbstractValue)
671 function ListValue.__init__(self, ...)
672 AbstractValue.__init__(self, ...)
673 self.template = "cbi/lvalue"
678 self.widget = "select"
681 function ListValue.value(self, key, val)
683 table.insert(self.keylist, tostring(key))
684 table.insert(self.vallist, tostring(val))
687 function ListValue.validate(self, val)
688 if luci.util.contains(self.keylist, val) then
698 MultiValue - Multiple delimited values
699 widget: The widget that will be used (select, checkbox)
700 delimiter: The delimiter that will separate the values (default: " ")
702 MultiValue = class(AbstractValue)
704 function MultiValue.__init__(self, ...)
705 AbstractValue.__init__(self, ...)
706 self.template = "cbi/mvalue"
710 self.widget = "checkbox"
714 function MultiValue.value(self, key, val)
716 table.insert(self.keylist, tostring(key))
717 table.insert(self.vallist, tostring(val))
720 function MultiValue.valuelist(self, section)
721 local val = self:cfgvalue(section)
723 if not(type(val) == "string") then
727 return luci.util.split(val, self.delimiter)
730 function MultiValue.validate(self, val)
731 if not(type(val) == "string") then
737 for value in val:gmatch("[^\n]+") do
738 if luci.util.contains(self.keylist, value) then
739 result = result .. self.delimiter .. value
743 if result:len() > 0 then
744 return result:sub(self.delimiter:len() + 1)