libs/cbi: Some fixes
[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 CREATE_PREFIX = "cbi.cts."
43 REMOVE_PREFIX = "cbi.rts."
44
45 -- Loads a CBI map from given file, creating an environment and returns it
46 function load(cbimap, ...)
47 require("luci.fs")
48 require("luci.i18n")
49 require("luci.config")
50 require("luci.util")
51
52 local cbidir = luci.util.libpath() .. "/model/cbi/"
53 local func, err = loadfile(cbidir..cbimap..".lua")
54
55 if not func then
56 return nil
57 end
58
59 luci.i18n.loadc("cbi")
60
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", {...})
66
67 local maps = {func()}
68
69 for i, map in ipairs(maps) do
70 if not instanceof(map, Node) then
71 error("CBI map returns no valid map object!")
72 return nil
73 end
74 end
75
76 return maps
77 end
78
79 -- Node pseudo abstract class
80 Node = class()
81
82 function Node.__init__(self, title, description)
83 self.children = {}
84 self.title = title or ""
85 self.description = description or ""
86 self.template = "cbi/node"
87 end
88
89 -- i18n helper
90 function Node._i18n(self, config, section, option, title, description)
91
92 -- i18n loaded?
93 if type(luci.i18n) == "table" then
94
95 local key = config:gsub("[^%w]+", "")
96
97 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
98 if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end
99
100 self.title = title or luci.i18n.translate( key, option or section or config )
101 self.description = description or luci.i18n.translate( key .. "_desc", "" )
102 end
103 end
104
105 -- Append child nodes
106 function Node.append(self, obj)
107 table.insert(self.children, obj)
108 end
109
110 -- Parse this node and its children
111 function Node.parse(self, ...)
112 for k, child in ipairs(self.children) do
113 child:parse(...)
114 end
115 end
116
117 -- Render this node
118 function Node.render(self, scope)
119 scope = scope or {}
120 scope.self = self
121
122 luci.template.render(self.template, scope)
123 end
124
125 -- Render the children
126 function Node.render_children(self, ...)
127 for k, node in ipairs(self.children) do
128 node:render(...)
129 end
130 end
131
132
133 --[[
134 A simple template element
135 ]]--
136 Template = class(Node)
137
138 function Template.__init__(self, template)
139 Node.__init__(self)
140 self.template = template
141 end
142
143 function Template.render(self)
144 luci.template.render(self.template, {self=self})
145 end
146
147
148 --[[
149 Map - A map describing a configuration file
150 ]]--
151 Map = class(Node)
152
153 function Map.__init__(self, config, ...)
154 Node.__init__(self, ...)
155 Node._i18n(self, config, nil, nil, ...)
156
157 self.config = config
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)
162 end
163 end
164
165
166 -- Chain foreign config
167 function Map.chain(self, config)
168 table.insert(self.parsechain, config)
169 end
170
171 -- Use optimized UCI writing
172 function Map.parse(self, ...)
173 Node.parse(self, ...)
174 for i, config in ipairs(self.parsechain) do
175 uci.save(config)
176 end
177 if luci.http.formvalue("cbi.apply") then
178 for i, config in ipairs(self.parsechain) do
179 uci.commit(config)
180 if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
181 luci.util.exec(luci.config.uci_oncommit[config])
182 end
183
184 -- Refresh data because commit changes section names
185 uci.unload(config)
186 uci.load(config)
187 end
188
189 -- Reparse sections
190 Node.parse(self, ...)
191
192 end
193 for i, config in ipairs(self.parsechain) do
194 uci.unload(config)
195 end
196 end
197
198 -- Creates a child section
199 function Map.section(self, class, ...)
200 if instanceof(class, AbstractSection) then
201 local obj = class(self, ...)
202 self:append(obj)
203 return obj
204 else
205 error("class must be a descendent of AbstractSection")
206 end
207 end
208
209 -- UCI add
210 function Map.add(self, sectiontype)
211 return uci.add(self.config, sectiontype)
212 end
213
214 -- UCI set
215 function Map.set(self, section, option, value)
216 if option then
217 return uci.set(self.config, section, option, value)
218 else
219 return uci.set(self.config, section, value)
220 end
221 end
222
223 -- UCI del
224 function Map.del(self, section, option)
225 if option then
226 return uci.delete(self.config, section, option)
227 else
228 return uci.delete(self.config, section)
229 end
230 end
231
232 -- UCI get
233 function Map.get(self, section, option)
234 if not section then
235 return uci.get_all(self.config)
236 elseif option then
237 return uci.get(self.config, section, option)
238 else
239 return uci.get_all(self.config, section)
240 end
241 end
242
243 -- UCI stateget
244 function Map.stateget(self, section, option)
245 return uci.get_statevalue(self.config, section, option)
246 end
247
248
249 --[[
250 Page - A simple node
251 ]]--
252
253 Page = class(Node)
254 Page.__init__ = Node.__init__
255 Page.parse = function() end
256
257
258 --[[
259 SimpleForm - A Simple non-UCI form
260 ]]--
261 SimpleForm = class(Node)
262
263 function SimpleForm.__init__(self, config, title, description, data)
264 Node.__init__(self, title, description)
265 self.config = config
266 self.data = data or {}
267 self.template = "cbi/simpleform"
268 self.dorender = true
269 end
270
271 function SimpleForm.parse(self, ...)
272 if luci.http.formvalue("cbi.submit") then
273 Node.parse(self, 1, ...)
274 end
275
276 local valid = true
277 for k, j in ipairs(self.children) do
278 for i, v in ipairs(j.children) do
279 valid = valid
280 and (not v.tag_missing or not v.tag_missing[1])
281 and (not v.tag_invalid or not v.tag_invalid[1])
282 end
283 end
284
285 local state =
286 not luci.http.formvalue("cbi.submit") and 0
287 or valid and 1
288 or -1
289
290 self.dorender = self:handle(state, self.data) ~= false
291 end
292
293 function SimpleForm.render(self, ...)
294 if self.dorender then
295 Node.render(self, ...)
296 end
297 end
298
299 function SimpleForm.section(self, class, ...)
300 if instanceof(class, AbstractSection) then
301 local obj = class(self, ...)
302 self:append(obj)
303 return obj
304 else
305 error("class must be a descendent of AbstractSection")
306 end
307 end
308
309 -- Creates a child field
310 function SimpleForm.field(self, class, ...)
311 local section
312 for k, v in ipairs(self.children) do
313 if instanceof(v, SimpleSection) then
314 section = v
315 break
316 end
317 end
318 if not section then
319 section = self:section(SimpleSection)
320 end
321
322 if instanceof(class, AbstractValue) then
323 local obj = class(self, ...)
324 obj.track_missing = true
325 section:append(obj)
326 return obj
327 else
328 error("class must be a descendent of AbstractValue")
329 end
330 end
331
332 function SimpleForm.set(self, section, option, value)
333 self.data[option] = value
334 end
335
336
337 function SimpleForm.del(self, section, option)
338 self.data[option] = nil
339 end
340
341
342 function SimpleForm.get(self, section, option)
343 return self.data[option]
344 end
345
346
347
348 --[[
349 AbstractSection
350 ]]--
351 AbstractSection = class(Node)
352
353 function AbstractSection.__init__(self, map, sectiontype, ...)
354 Node.__init__(self, ...)
355 self.sectiontype = sectiontype
356 self.map = map
357 self.config = map.config
358 self.optionals = {}
359 self.defaults = {}
360
361 self.optional = true
362 self.addremove = false
363 self.dynamic = false
364 end
365
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, ...)
370
371 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
372
373 self:append(obj)
374 return obj
375 else
376 error("class must be a descendent of AbstractValue")
377 end
378 end
379
380 -- Parse optional options
381 function AbstractSection.parse_optionals(self, section)
382 if not self.optional then
383 return
384 end
385
386 self.optionals[section] = {}
387
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
392 field = nil
393 else
394 table.insert(self.optionals[section], v)
395 end
396 end
397 end
398
399 if field and #field > 0 and self.dynamic then
400 self:add_dynamic(field)
401 end
402 end
403
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
408 end
409
410 -- Parse all dynamic options
411 function AbstractSection.parse_dynamic(self, section)
412 if not self.dynamic then
413 return
414 end
415
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
419 arr[k] = v
420 end
421
422 for key,val in pairs(arr) do
423 local create = true
424
425 for i,c in ipairs(self.children) do
426 if c.option == key then
427 create = false
428 end
429 end
430
431 if create and key:sub(1, 1) ~= "." then
432 self:add_dynamic(key, true)
433 end
434 end
435 end
436
437 -- Returns the section's UCI table
438 function AbstractSection.cfgvalue(self, section)
439 return self.map:get(section)
440 end
441
442 -- Removes the section
443 function AbstractSection.remove(self, section)
444 return self.map:del(section)
445 end
446
447 -- Creates the section
448 function AbstractSection.create(self, section)
449 local stat
450
451 if section then
452 stat = self.map:set(section, nil, self.sectiontype)
453 else
454 section = self.map:add(self.sectiontype)
455 stat = section
456 end
457
458 if stat then
459 for k,v in pairs(self.children) do
460 if v.default then
461 self.map:set(section, v.option, v.default)
462 end
463 end
464
465 for k,v in pairs(self.defaults) do
466 self.map:set(section, k, v)
467 end
468 end
469
470 return stat
471 end
472
473
474 SimpleSection = class(AbstractSection)
475
476 function SimpleSection.__init__(self, form, ...)
477 AbstractSection.__init__(self, form, nil, ...)
478 self.template = "cbi/nullsection"
479 end
480
481
482
483 --[[
484 NamedSection - A fixed configuration section defined by its name
485 ]]--
486 NamedSection = class(AbstractSection)
487
488 function NamedSection.__init__(self, map, section, type, ...)
489 AbstractSection.__init__(self, map, type, ...)
490 Node._i18n(self, map.config, section, nil, ...)
491
492 self.template = "cbi/nsection"
493 self.section = section
494 self.addremove = false
495 end
496
497 function NamedSection.parse(self)
498 local s = self.section
499 local active = self:cfgvalue(s)
500
501
502 if self.addremove then
503 local path = self.config.."."..s
504 if active then -- Remove the section
505 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
506 return
507 end
508 else -- Create and apply default values
509 if luci.http.formvalue("cbi.cns."..path) then
510 self:create(s)
511 return
512 end
513 end
514 end
515
516 if active then
517 AbstractSection.parse_dynamic(self, s)
518 if luci.http.formvalue("cbi.submit") then
519 Node.parse(self, s)
520 end
521 AbstractSection.parse_optionals(self, s)
522 end
523 end
524
525
526 --[[
527 TypedSection - A (set of) configuration section(s) defined by the type
528 addremove: Defines whether the user can add/remove sections of this type
529 anonymous: Allow creating anonymous sections
530 validate: a validation function returning nil if the section is invalid
531 ]]--
532 TypedSection = class(AbstractSection)
533
534 function TypedSection.__init__(self, map, type, ...)
535 AbstractSection.__init__(self, map, type, ...)
536 Node._i18n(self, map.config, type, nil, ...)
537
538 self.template = "cbi/tsection"
539 self.deps = {}
540
541 self.anonymous = false
542 end
543
544 -- Return all matching UCI sections for this TypedSection
545 function TypedSection.cfgsections(self)
546 local sections = {}
547 uci.foreach(self.map.config, self.sectiontype,
548 function (section)
549 if self:checkscope(section[".name"]) then
550 table.insert(sections, section[".name"])
551 end
552 end)
553
554 return sections
555 end
556
557 -- Limits scope to sections that have certain option => value pairs
558 function TypedSection.depends(self, option, value)
559 table.insert(self.deps, {option=option, value=value})
560 end
561
562 function TypedSection.parse(self)
563 if self.addremove then
564 -- Create
565 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
566 local name = luci.http.formvalue(crval)
567 if self.anonymous then
568 if name then
569 self:create()
570 end
571 else
572 if name then
573 -- Ignore if it already exists
574 if self:cfgvalue(name) then
575 name = nil;
576 end
577
578 name = self:checkscope(name)
579
580 if not name then
581 self.err_invalid = true
582 end
583
584 if name and name:len() > 0 then
585 self:create(name)
586 end
587 end
588 end
589
590 -- Remove
591 crval = REMOVE_PREFIX .. self.config
592 name = luci.http.formvaluetable(crval)
593 for k,v in pairs(name) do
594 if self:cfgvalue(k) and self:checkscope(k) then
595 self:remove(k)
596 end
597 end
598 end
599
600 for i, k in ipairs(self:cfgsections()) do
601 AbstractSection.parse_dynamic(self, k)
602 if luci.http.formvalue("cbi.submit") then
603 Node.parse(self, k)
604 end
605 AbstractSection.parse_optionals(self, k)
606 end
607 end
608
609 -- Verifies scope of sections
610 function TypedSection.checkscope(self, section)
611 -- Check if we are not excluded
612 if self.filter and not self:filter(section) then
613 return nil
614 end
615
616 -- Check if at least one dependency is met
617 if #self.deps > 0 and self:cfgvalue(section) then
618 local stat = false
619
620 for k, v in ipairs(self.deps) do
621 if self:cfgvalue(section)[v.option] == v.value then
622 stat = true
623 end
624 end
625
626 if not stat then
627 return nil
628 end
629 end
630
631 return self:validate(section)
632 end
633
634
635 -- Dummy validate function
636 function TypedSection.validate(self, section)
637 return section
638 end
639
640
641 --[[
642 AbstractValue - An abstract Value Type
643 null: Value can be empty
644 valid: A function returning the value if it is valid otherwise nil
645 depends: A table of option => value pairs of which one must be true
646 default: The default value
647 size: The size of the input fields
648 rmempty: Unset value if empty
649 optional: This value is optional (see AbstractSection.optionals)
650 ]]--
651 AbstractValue = class(Node)
652
653 function AbstractValue.__init__(self, map, option, ...)
654 Node.__init__(self, ...)
655 self.option = option
656 self.map = map
657 self.config = map.config
658 self.tag_invalid = {}
659 self.tag_missing = {}
660 self.deps = {}
661
662 self.track_missing = false
663 self.rmempty = false
664 self.default = nil
665 self.size = nil
666 self.optional = false
667 self.stateful = false
668 end
669
670 -- Add a dependencie to another section field
671 function AbstractValue.depends(self, field, value)
672 table.insert(self.deps, {field=field, value=value})
673 end
674
675 -- Return whether this object should be created
676 function AbstractValue.formcreated(self, section)
677 local key = "cbi.opt."..self.config.."."..section
678 return (luci.http.formvalue(key) == self.option)
679 end
680
681 -- Returns the formvalue for this object
682 function AbstractValue.formvalue(self, section)
683 local key = "cbid."..self.map.config.."."..section.."."..self.option
684 return luci.http.formvalue(key)
685 end
686
687 function AbstractValue.additional(self, value)
688 self.optional = value
689 end
690
691 function AbstractValue.mandatory(self, value)
692 self.rmempty = not value
693 end
694
695 function AbstractValue.parse(self, section)
696 local fvalue = self:formvalue(section)
697 local cvalue = self:cfgvalue(section)
698
699 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
700 fvalue = self:transform(self:validate(fvalue, section))
701 if not fvalue then
702 self.tag_invalid[section] = true
703 end
704 if fvalue and not (fvalue == cvalue) then
705 self:write(section, fvalue)
706 end
707 else -- Unset the UCI or error
708 if self.rmempty or self.optional then
709 self:remove(section)
710 elseif self.track_missing and not fvalue or fvalue ~= cvalue then
711 self.tag_missing[section] = true
712 end
713 end
714 end
715
716 -- Render if this value exists or if it is mandatory
717 function AbstractValue.render(self, s, scope)
718 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
719 scope = scope or {}
720 scope.section = s
721 scope.cbid = "cbid." .. self.config ..
722 "." .. s ..
723 "." .. self.option
724
725 scope.ifattr = function(cond,key,val)
726 if cond then
727 return string.format(
728 ' %s="%s"', tostring(key),
729 tostring( val
730 or scope[key]
731 or (type(self[key]) ~= "function" and self[key])
732 or "" )
733 )
734 else
735 return ''
736 end
737 end
738
739 scope.attr = function(...)
740 return scope.ifattr( true, ... )
741 end
742
743 Node.render(self, scope)
744 end
745 end
746
747 -- Return the UCI value of this object
748 function AbstractValue.cfgvalue(self, section)
749 return self.stateful
750 and self.map:stateget(section, self.option)
751 or self.map:get(section, self.option)
752 end
753
754 -- Validate the form value
755 function AbstractValue.validate(self, value)
756 return value
757 end
758
759 AbstractValue.transform = AbstractValue.validate
760
761
762 -- Write to UCI
763 function AbstractValue.write(self, section, value)
764 return self.map:set(section, self.option, value)
765 end
766
767 -- Remove from UCI
768 function AbstractValue.remove(self, section)
769 return self.map:del(section, self.option)
770 end
771
772
773
774
775 --[[
776 Value - A one-line value
777 maxlength: The maximum length
778 ]]--
779 Value = class(AbstractValue)
780
781 function Value.__init__(self, ...)
782 AbstractValue.__init__(self, ...)
783 self.template = "cbi/value"
784 self.keylist = {}
785 self.vallist = {}
786 end
787
788 function Value.value(self, key, val)
789 val = val or key
790 table.insert(self.keylist, tostring(key))
791 table.insert(self.vallist, tostring(val))
792 end
793
794
795 -- DummyValue - This does nothing except being there
796 DummyValue = class(AbstractValue)
797
798 function DummyValue.__init__(self, map, ...)
799 AbstractValue.__init__(self, map, ...)
800 self.template = "cbi/dvalue"
801 self.value = nil
802 end
803
804 function DummyValue.parse(self)
805
806 end
807
808
809 --[[
810 Flag - A flag being enabled or disabled
811 ]]--
812 Flag = class(AbstractValue)
813
814 function Flag.__init__(self, ...)
815 AbstractValue.__init__(self, ...)
816 self.template = "cbi/fvalue"
817
818 self.enabled = "1"
819 self.disabled = "0"
820 end
821
822 -- A flag can only have two states: set or unset
823 function Flag.parse(self, section)
824 local fvalue = self:formvalue(section)
825
826 if fvalue then
827 fvalue = self.enabled
828 else
829 fvalue = self.disabled
830 end
831
832 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
833 if not(fvalue == self:cfgvalue(section)) then
834 self:write(section, fvalue)
835 end
836 else
837 self:remove(section)
838 end
839 end
840
841
842
843 --[[
844 ListValue - A one-line value predefined in a list
845 widget: The widget that will be used (select, radio)
846 ]]--
847 ListValue = class(AbstractValue)
848
849 function ListValue.__init__(self, ...)
850 AbstractValue.__init__(self, ...)
851 self.template = "cbi/lvalue"
852 self.keylist = {}
853 self.vallist = {}
854
855 self.size = 1
856 self.widget = "select"
857 end
858
859 function ListValue.value(self, key, val)
860 val = val or key
861 table.insert(self.keylist, tostring(key))
862 table.insert(self.vallist, tostring(val))
863 end
864
865 function ListValue.validate(self, val)
866 if luci.util.contains(self.keylist, val) then
867 return val
868 else
869 return nil
870 end
871 end
872
873
874
875 --[[
876 MultiValue - Multiple delimited values
877 widget: The widget that will be used (select, checkbox)
878 delimiter: The delimiter that will separate the values (default: " ")
879 ]]--
880 MultiValue = class(AbstractValue)
881
882 function MultiValue.__init__(self, ...)
883 AbstractValue.__init__(self, ...)
884 self.template = "cbi/mvalue"
885 self.keylist = {}
886 self.vallist = {}
887
888 self.widget = "checkbox"
889 self.delimiter = " "
890 end
891
892 function MultiValue.render(self, ...)
893 if self.widget == "select" and not self.size then
894 self.size = #self.vallist
895 end
896
897 AbstractValue.render(self, ...)
898 end
899
900 function MultiValue.value(self, key, val)
901 val = val or key
902 table.insert(self.keylist, tostring(key))
903 table.insert(self.vallist, tostring(val))
904 end
905
906 function MultiValue.valuelist(self, section)
907 local val = self:cfgvalue(section)
908
909 if not(type(val) == "string") then
910 return {}
911 end
912
913 return luci.util.split(val, self.delimiter)
914 end
915
916 function MultiValue.validate(self, val)
917 val = (type(val) == "table") and val or {val}
918
919 local result
920
921 for i, value in ipairs(val) do
922 if luci.util.contains(self.keylist, value) then
923 result = result and (result .. self.delimiter .. value) or value
924 end
925 end
926
927 return result
928 end
929
930 --[[
931 TextValue - A multi-line value
932 rows: Rows
933 ]]--
934 TextValue = class(AbstractValue)
935
936 function TextValue.__init__(self, ...)
937 AbstractValue.__init__(self, ...)
938 self.template = "cbi/tvalue"
939 end