libs/cbi: Major Improvements
[project/luci.git] / libs / cbi / luasrc / cbi.lua
1 --[[
2 LuCI - Configuration Bind Interface
3
4 Description:
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
7
8 FileId:
9 $Id$
10
11 License:
12 Copyright 2008 Steven Barth <steven@midlink.org>
13
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
17
18 http://www.apache.org/licenses/LICENSE-2.0
19
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.
25
26 ]]--
27 module("luci.cbi", package.seeall)
28
29 require("luci.template")
30 require("luci.util")
31 require("luci.http")
32 require("luci.model.uci")
33
34 local uci = luci.model.uci
35 local class = luci.util.class
36 local instanceof = luci.util.instanceof
37
38 FORM_NODATA = 0
39 FORM_VALID = 1
40 FORM_INVALID = -1
41
42 -- Loads a CBI map from given file, creating an environment and returns it
43 function load(cbimap, ...)
44 require("luci.fs")
45 require("luci.i18n")
46 require("luci.config")
47 require("luci.util")
48
49 local cbidir = luci.util.libpath() .. "/model/cbi/"
50 local func, err = loadfile(cbidir..cbimap..".lua")
51
52 if not func then
53 return nil
54 end
55
56 luci.i18n.loadc("cbi")
57
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", {...})
63
64 local maps = {func()}
65
66 for i, map in ipairs(maps) do
67 if not instanceof(map, Node) then
68 error("CBI map returns no valid map object!")
69 return nil
70 end
71 end
72
73 return maps
74 end
75
76 -- Node pseudo abstract class
77 Node = class()
78
79 function Node.__init__(self, title, description)
80 self.children = {}
81 self.title = title or ""
82 self.description = description or ""
83 self.template = "cbi/node"
84 end
85
86 -- i18n helper
87 function Node._i18n(self, config, section, option, title, description)
88
89 -- i18n loaded?
90 if type(luci.i18n) == "table" then
91
92 local key = config:gsub("[^%w]+", "")
93
94 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
95 if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end
96
97 self.title = title or luci.i18n.translate( key, option or section or config )
98 self.description = description or luci.i18n.translate( key .. "_desc", "" )
99 end
100 end
101
102 -- Append child nodes
103 function Node.append(self, obj)
104 table.insert(self.children, obj)
105 end
106
107 -- Parse this node and its children
108 function Node.parse(self, ...)
109 for k, child in ipairs(self.children) do
110 child:parse(...)
111 end
112 end
113
114 -- Render this node
115 function Node.render(self, scope)
116 scope = scope or {}
117 scope.self = self
118
119 luci.template.render(self.template, scope)
120 end
121
122 -- Render the children
123 function Node.render_children(self, ...)
124 for k, node in ipairs(self.children) do
125 node:render(...)
126 end
127 end
128
129
130 --[[
131 A simple template element
132 ]]--
133 Template = class(Node)
134
135 function Template.__init__(self, template)
136 Node.__init__(self)
137 self.template = template
138 end
139
140
141 --[[
142 Map - A map describing a configuration file
143 ]]--
144 Map = class(Node)
145
146 function Map.__init__(self, config, ...)
147 Node.__init__(self, ...)
148 Node._i18n(self, config, nil, nil, ...)
149
150 self.config = config
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)
155 end
156 end
157
158
159 -- Chain foreign config
160 function Map.chain(self, config)
161 table.insert(self.parsechain, config)
162 end
163
164 -- Use optimized UCI writing
165 function Map.parse(self, ...)
166 Node.parse(self, ...)
167 for i, config in ipairs(self.parsechain) do
168 uci.save(config)
169 end
170 if luci.http.formvalue("cbi.apply") then
171 for i, config in ipairs(self.parsechain) do
172 uci.commit(config)
173 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
174 luci.util.exec(luci.config.uci_oncommit[config])
175 end
176
177 -- Refresh data because commit changes section names
178 uci.unload(config)
179 uci.load(config)
180 end
181
182 -- Reparse sections
183 Node.parse(self, ...)
184
185 end
186 for i, config in ipairs(self.parsechain) do
187 uci.unload(config)
188 end
189 end
190
191 -- Creates a child section
192 function Map.section(self, class, ...)
193 if instanceof(class, AbstractSection) then
194 local obj = class(self, ...)
195 self:append(obj)
196 return obj
197 else
198 error("class must be a descendent of AbstractSection")
199 end
200 end
201
202 -- UCI add
203 function Map.add(self, sectiontype)
204 return uci.add(self.config, sectiontype)
205 end
206
207 -- UCI set
208 function Map.set(self, section, option, value)
209 if option then
210 return uci.set(self.config, section, option, value)
211 else
212 return uci.set(self.config, section, value)
213 end
214 end
215
216 -- UCI del
217 function Map.del(self, section, option)
218 if option then
219 return uci.delete(self.config, section, option)
220 else
221 return uci.delete(self.config, section)
222 end
223 end
224
225 -- UCI get
226 function Map.get(self, section, option)
227 if not section then
228 return uci.get_all(self.config)
229 elseif option then
230 return uci.get(self.config, section, option)
231 else
232 return uci.get_all(self.config, section)
233 end
234 end
235
236
237 --[[
238 SimpleForm - A Simple non-UCI form
239 ]]--
240 SimpleForm = class(Node)
241
242 function SimpleForm.__init__(self, config, title, description, data)
243 Node.__init__(self, title, description)
244 self.config = config
245 self.data = data or {}
246 self.template = "cbi/simpleform"
247 self.dorender = true
248 end
249
250 function SimpleForm.parse(self, ...)
251 Node.parse(self, 1, ...)
252
253 local valid = true
254 for i, v in ipairs(self.children) do
255 valid = valid and not v.tag_missing[1] and not v.tag_invalid[1]
256 end
257
258 local state =
259 not luci.http.formvalue("cbi.submit") and 0
260 or valid and 1
261 or -1
262
263 self.dorender = self:handle(state)
264 end
265
266 function SimpleForm.render(self, ...)
267 if self.dorender then
268 Node.render(self, ...)
269 end
270 end
271
272 -- Creates a child section
273 function SimpleForm.field(self, class, ...)
274 if instanceof(class, AbstractValue) then
275 local obj = class(self, ...)
276 self:append(obj)
277 return obj
278 else
279 error("class must be a descendent of AbstractValue")
280 end
281 end
282
283 function SimpleForm.set(self, section, option, value)
284 self.data[option] = value
285 end
286
287
288 function SimpleForm.del(self, section, option)
289 self.data[option] = nil
290 end
291
292
293 function SimpleForm.get(self, section, option)
294 return self.data[option]
295 end
296
297
298
299 --[[
300 AbstractSection
301 ]]--
302 AbstractSection = class(Node)
303
304 function AbstractSection.__init__(self, map, sectiontype, ...)
305 Node.__init__(self, ...)
306 self.sectiontype = sectiontype
307 self.map = map
308 self.config = map.config
309 self.optionals = {}
310 self.defaults = {}
311
312 self.optional = true
313 self.addremove = false
314 self.dynamic = false
315 end
316
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, ...)
321
322 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
323
324 self:append(obj)
325 return obj
326 else
327 error("class must be a descendent of AbstractValue")
328 end
329 end
330
331 -- Parse optional options
332 function AbstractSection.parse_optionals(self, section)
333 if not self.optional then
334 return
335 end
336
337 self.optionals[section] = {}
338
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
343 field = nil
344 else
345 table.insert(self.optionals[section], v)
346 end
347 end
348 end
349
350 if field and #field > 0 and self.dynamic then
351 self:add_dynamic(field)
352 end
353 end
354
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
359 end
360
361 -- Parse all dynamic options
362 function AbstractSection.parse_dynamic(self, section)
363 if not self.dynamic then
364 return
365 end
366
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
370 arr[k] = v
371 end
372
373 for key,val in pairs(arr) do
374 local create = true
375
376 for i,c in ipairs(self.children) do
377 if c.option == key then
378 create = false
379 end
380 end
381
382 if create and key:sub(1, 1) ~= "." then
383 self:add_dynamic(key, true)
384 end
385 end
386 end
387
388 -- Returns the section's UCI table
389 function AbstractSection.cfgvalue(self, section)
390 return self.map:get(section)
391 end
392
393 -- Removes the section
394 function AbstractSection.remove(self, section)
395 return self.map:del(section)
396 end
397
398 -- Creates the section
399 function AbstractSection.create(self, section)
400 local stat
401
402 if section then
403 stat = self.map:set(section, nil, self.sectiontype)
404 else
405 section = self.map:add(self.sectiontype)
406 stat = section
407 end
408
409 if stat then
410 for k,v in pairs(self.children) do
411 if v.default then
412 self.map:set(section, v.option, v.default)
413 end
414 end
415
416 for k,v in pairs(self.defaults) do
417 self.map:set(section, k, v)
418 end
419 end
420
421 return stat
422 end
423
424
425
426 --[[
427 NamedSection - A fixed configuration section defined by its name
428 ]]--
429 NamedSection = class(AbstractSection)
430
431 function NamedSection.__init__(self, map, section, type, ...)
432 AbstractSection.__init__(self, map, type, ...)
433 Node._i18n(self, map.config, section, nil, ...)
434
435 self.template = "cbi/nsection"
436 self.section = section
437 self.addremove = false
438 end
439
440 function NamedSection.parse(self)
441 local s = self.section
442 local active = self:cfgvalue(s)
443
444
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
449 return
450 end
451 else -- Create and apply default values
452 if luci.http.formvalue("cbi.cns."..path) then
453 self:create(s)
454 return
455 end
456 end
457 end
458
459 if active then
460 AbstractSection.parse_dynamic(self, s)
461 if luci.http.formvalue("cbi.submit") then
462 Node.parse(self, s)
463 end
464 AbstractSection.parse_optionals(self, s)
465 end
466 end
467
468
469 --[[
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
474 ]]--
475 TypedSection = class(AbstractSection)
476
477 function TypedSection.__init__(self, map, type, ...)
478 AbstractSection.__init__(self, map, type, ...)
479 Node._i18n(self, map.config, type, nil, ...)
480
481 self.template = "cbi/tsection"
482 self.deps = {}
483
484 self.anonymous = false
485 end
486
487 -- Return all matching UCI sections for this TypedSection
488 function TypedSection.cfgsections(self)
489 local sections = {}
490 uci.foreach(self.map.config, self.sectiontype,
491 function (section)
492 if self:checkscope(section[".name"]) then
493 table.insert(sections, section[".name"])
494 end
495 end)
496
497 return sections
498 end
499
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})
503 end
504
505 function TypedSection.parse(self)
506 if self.addremove then
507 -- Create
508 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
509 local name = luci.http.formvalue(crval)
510 if self.anonymous then
511 if name then
512 self:create()
513 end
514 else
515 if name then
516 -- Ignore if it already exists
517 if self:cfgvalue(name) then
518 name = nil;
519 end
520
521 name = self:checkscope(name)
522
523 if not name then
524 self.err_invalid = true
525 end
526
527 if name and name:len() > 0 then
528 self:create(name)
529 end
530 end
531 end
532
533 -- Remove
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
538 self:remove(k)
539 end
540 end
541 end
542
543 for i, k in ipairs(self:cfgsections()) do
544 AbstractSection.parse_dynamic(self, k)
545 if luci.http.formvalue("cbi.submit") then
546 Node.parse(self, k)
547 end
548 AbstractSection.parse_optionals(self, k)
549 end
550 end
551
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
556 return nil
557 end
558
559 -- Check if at least one dependency is met
560 if #self.deps > 0 and self:cfgvalue(section) then
561 local stat = false
562
563 for k, v in ipairs(self.deps) do
564 if self:cfgvalue(section)[v.option] == v.value then
565 stat = true
566 end
567 end
568
569 if not stat then
570 return nil
571 end
572 end
573
574 return self:validate(section)
575 end
576
577
578 -- Dummy validate function
579 function TypedSection.validate(self, section)
580 return section
581 end
582
583
584 --[[
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)
593 ]]--
594 AbstractValue = class(Node)
595
596 function AbstractValue.__init__(self, map, option, ...)
597 Node.__init__(self, ...)
598 self.option = option
599 self.map = map
600 self.config = map.config
601 self.tag_invalid = {}
602 self.tag_missing = {}
603 self.deps = {}
604
605 self.rmempty = false
606 self.default = nil
607 self.size = nil
608 self.optional = false
609 end
610
611 -- Add a dependencie to another section field
612 function AbstractValue.depends(self, field, value)
613 table.insert(self.deps, {field=field, value=value})
614 end
615
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)
620 end
621
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)
626 end
627
628 function AbstractValue.additional(self, value)
629 self.optional = value
630 end
631
632 function AbstractValue.mandatory(self, value)
633 self.rmempty = not value
634 end
635
636 function AbstractValue.parse(self, section)
637 local fvalue = self:formvalue(section)
638 local cvalue = self:cfgvalue(section)
639
640 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
641 fvalue = self:transform(self:validate(fvalue))
642 if not fvalue then
643 self.tag_invalid[section] = true
644 end
645 if fvalue and not (fvalue == self:cfgvalue(section)) then
646 self:write(section, fvalue)
647 end
648 else -- Unset the UCI or error
649 if self.rmempty or self.optional then
650 self:remove(section)
651 elseif not fvalue or fvalue ~= cvalue then
652 self.tag_missing[section] = true
653 end
654 end
655 end
656
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
660 scope = scope or {}
661 scope.section = s
662 scope.cbid = "cbid." .. self.config ..
663 "." .. s ..
664 "." .. self.option
665
666 scope.ifattr = function(cond,key,val)
667 if cond then
668 return string.format(
669 ' %s="%s"', tostring(key),
670 tostring( val
671 or scope[key]
672 or (type(self[key]) ~= "function" and self[key])
673 or "" )
674 )
675 else
676 return ''
677 end
678 end
679
680 scope.attr = function(...)
681 return scope.ifattr( true, ... )
682 end
683
684 Node.render(self, scope)
685 end
686 end
687
688 -- Return the UCI value of this object
689 function AbstractValue.cfgvalue(self, section)
690 return self.map:get(section, self.option)
691 end
692
693 -- Validate the form value
694 function AbstractValue.validate(self, value)
695 return value
696 end
697
698 AbstractValue.transform = AbstractValue.validate
699
700
701 -- Write to UCI
702 function AbstractValue.write(self, section, value)
703 return self.map:set(section, self.option, value)
704 end
705
706 -- Remove from UCI
707 function AbstractValue.remove(self, section)
708 return self.map:del(section, self.option)
709 end
710
711
712
713
714 --[[
715 Value - A one-line value
716 maxlength: The maximum length
717 ]]--
718 Value = class(AbstractValue)
719
720 function Value.__init__(self, ...)
721 AbstractValue.__init__(self, ...)
722 self.template = "cbi/value"
723 self.keylist = {}
724 self.vallist = {}
725 end
726
727 function Value.value(self, key, val)
728 val = val or key
729 table.insert(self.keylist, tostring(key))
730 table.insert(self.vallist, tostring(val))
731 end
732
733
734 -- DummyValue - This does nothing except being there
735 DummyValue = class(AbstractValue)
736
737 function DummyValue.__init__(self, map, ...)
738 AbstractValue.__init__(self, map, ...)
739 self.template = "cbi/dvalue"
740 self.value = nil
741 end
742
743 function DummyValue.parse(self)
744
745 end
746
747
748 --[[
749 Flag - A flag being enabled or disabled
750 ]]--
751 Flag = class(AbstractValue)
752
753 function Flag.__init__(self, ...)
754 AbstractValue.__init__(self, ...)
755 self.template = "cbi/fvalue"
756
757 self.enabled = "1"
758 self.disabled = "0"
759 end
760
761 -- A flag can only have two states: set or unset
762 function Flag.parse(self, section)
763 local fvalue = self:formvalue(section)
764
765 if fvalue then
766 fvalue = self.enabled
767 else
768 fvalue = self.disabled
769 end
770
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)
774 end
775 else
776 self:remove(section)
777 end
778 end
779
780
781
782 --[[
783 ListValue - A one-line value predefined in a list
784 widget: The widget that will be used (select, radio)
785 ]]--
786 ListValue = class(AbstractValue)
787
788 function ListValue.__init__(self, ...)
789 AbstractValue.__init__(self, ...)
790 self.template = "cbi/lvalue"
791 self.keylist = {}
792 self.vallist = {}
793
794 self.size = 1
795 self.widget = "select"
796 end
797
798 function ListValue.value(self, key, val)
799 val = val or key
800 table.insert(self.keylist, tostring(key))
801 table.insert(self.vallist, tostring(val))
802 end
803
804 function ListValue.validate(self, val)
805 if luci.util.contains(self.keylist, val) then
806 return val
807 else
808 return nil
809 end
810 end
811
812
813
814 --[[
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: " ")
818 ]]--
819 MultiValue = class(AbstractValue)
820
821 function MultiValue.__init__(self, ...)
822 AbstractValue.__init__(self, ...)
823 self.template = "cbi/mvalue"
824 self.keylist = {}
825 self.vallist = {}
826
827 self.widget = "checkbox"
828 self.delimiter = " "
829 end
830
831 function MultiValue.render(self, ...)
832 if self.widget == "select" and not self.size then
833 self.size = #self.vallist
834 end
835
836 AbstractValue.render(self, ...)
837 end
838
839 function MultiValue.value(self, key, val)
840 val = val or key
841 table.insert(self.keylist, tostring(key))
842 table.insert(self.vallist, tostring(val))
843 end
844
845 function MultiValue.valuelist(self, section)
846 local val = self:cfgvalue(section)
847
848 if not(type(val) == "string") then
849 return {}
850 end
851
852 return luci.util.split(val, self.delimiter)
853 end
854
855 function MultiValue.validate(self, val)
856 val = (type(val) == "table") and val or {val}
857
858 local result
859
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
863 end
864 end
865
866 return result
867 end