7bcfceb2ef4794ed1dfa2695be7da357db9155ba
[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 Table = class(AbstractSection)
483
484 function Table.__init__(self, form, data, ...)
485 local datasource = {}
486 self.data = data
487
488 function datasource.get(self, section, option)
489 return data[option]
490 end
491
492 AbstractSection.__init__(self, datasource, nil, ...)
493 end
494
495 function Table.cfgsections(self)
496 local sections = {}
497
498 for i, v in pairs(self.data) do
499 table.insert(sections, i)
500 end
501
502 return sections
503 end
504
505
506
507 --[[
508 NamedSection - A fixed configuration section defined by its name
509 ]]--
510 NamedSection = class(AbstractSection)
511
512 function NamedSection.__init__(self, map, section, type, ...)
513 AbstractSection.__init__(self, map, type, ...)
514 Node._i18n(self, map.config, section, nil, ...)
515
516 self.template = "cbi/nsection"
517 self.section = section
518 self.addremove = false
519 end
520
521 function NamedSection.parse(self)
522 local s = self.section
523 local active = self:cfgvalue(s)
524
525
526 if self.addremove then
527 local path = self.config.."."..s
528 if active then -- Remove the section
529 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
530 return
531 end
532 else -- Create and apply default values
533 if luci.http.formvalue("cbi.cns."..path) then
534 self:create(s)
535 return
536 end
537 end
538 end
539
540 if active then
541 AbstractSection.parse_dynamic(self, s)
542 if luci.http.formvalue("cbi.submit") then
543 Node.parse(self, s)
544 end
545 AbstractSection.parse_optionals(self, s)
546 end
547 end
548
549
550 --[[
551 TypedSection - A (set of) configuration section(s) defined by the type
552 addremove: Defines whether the user can add/remove sections of this type
553 anonymous: Allow creating anonymous sections
554 validate: a validation function returning nil if the section is invalid
555 ]]--
556 TypedSection = class(AbstractSection)
557
558 function TypedSection.__init__(self, map, type, ...)
559 AbstractSection.__init__(self, map, type, ...)
560 Node._i18n(self, map.config, type, nil, ...)
561
562 self.template = "cbi/tsection"
563 self.deps = {}
564
565 self.anonymous = false
566 end
567
568 -- Return all matching UCI sections for this TypedSection
569 function TypedSection.cfgsections(self)
570 local sections = {}
571 uci.foreach(self.map.config, self.sectiontype,
572 function (section)
573 if self:checkscope(section[".name"]) then
574 table.insert(sections, section[".name"])
575 end
576 end)
577
578 return sections
579 end
580
581 -- Limits scope to sections that have certain option => value pairs
582 function TypedSection.depends(self, option, value)
583 table.insert(self.deps, {option=option, value=value})
584 end
585
586 function TypedSection.parse(self)
587 if self.addremove then
588 -- Create
589 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
590 local name = luci.http.formvalue(crval)
591 if self.anonymous then
592 if name then
593 self:create()
594 end
595 else
596 if name then
597 -- Ignore if it already exists
598 if self:cfgvalue(name) then
599 name = nil;
600 end
601
602 name = self:checkscope(name)
603
604 if not name then
605 self.err_invalid = true
606 end
607
608 if name and name:len() > 0 then
609 self:create(name)
610 end
611 end
612 end
613
614 -- Remove
615 crval = REMOVE_PREFIX .. self.config
616 name = luci.http.formvaluetable(crval)
617 for k,v in pairs(name) do
618 if self:cfgvalue(k) and self:checkscope(k) then
619 self:remove(k)
620 end
621 end
622 end
623
624 for i, k in ipairs(self:cfgsections()) do
625 AbstractSection.parse_dynamic(self, k)
626 if luci.http.formvalue("cbi.submit") then
627 Node.parse(self, k)
628 end
629 AbstractSection.parse_optionals(self, k)
630 end
631 end
632
633 -- Verifies scope of sections
634 function TypedSection.checkscope(self, section)
635 -- Check if we are not excluded
636 if self.filter and not self:filter(section) then
637 return nil
638 end
639
640 -- Check if at least one dependency is met
641 if #self.deps > 0 and self:cfgvalue(section) then
642 local stat = false
643
644 for k, v in ipairs(self.deps) do
645 if self:cfgvalue(section)[v.option] == v.value then
646 stat = true
647 end
648 end
649
650 if not stat then
651 return nil
652 end
653 end
654
655 return self:validate(section)
656 end
657
658
659 -- Dummy validate function
660 function TypedSection.validate(self, section)
661 return section
662 end
663
664
665 --[[
666 AbstractValue - An abstract Value Type
667 null: Value can be empty
668 valid: A function returning the value if it is valid otherwise nil
669 depends: A table of option => value pairs of which one must be true
670 default: The default value
671 size: The size of the input fields
672 rmempty: Unset value if empty
673 optional: This value is optional (see AbstractSection.optionals)
674 ]]--
675 AbstractValue = class(Node)
676
677 function AbstractValue.__init__(self, map, option, ...)
678 Node.__init__(self, ...)
679 self.option = option
680 self.map = map
681 self.config = map.config
682 self.tag_invalid = {}
683 self.tag_missing = {}
684 self.deps = {}
685
686 self.track_missing = false
687 self.rmempty = false
688 self.default = nil
689 self.size = nil
690 self.optional = false
691 self.stateful = false
692 end
693
694 -- Add a dependencie to another section field
695 function AbstractValue.depends(self, field, value)
696 table.insert(self.deps, {field=field, value=value})
697 end
698
699 -- Return whether this object should be created
700 function AbstractValue.formcreated(self, section)
701 local key = "cbi.opt."..self.config.."."..section
702 return (luci.http.formvalue(key) == self.option)
703 end
704
705 -- Returns the formvalue for this object
706 function AbstractValue.formvalue(self, section)
707 local key = "cbid."..self.map.config.."."..section.."."..self.option
708 return luci.http.formvalue(key)
709 end
710
711 function AbstractValue.additional(self, value)
712 self.optional = value
713 end
714
715 function AbstractValue.mandatory(self, value)
716 self.rmempty = not value
717 end
718
719 function AbstractValue.parse(self, section)
720 local fvalue = self:formvalue(section)
721 local cvalue = self:cfgvalue(section)
722
723 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
724 fvalue = self:transform(self:validate(fvalue, section))
725 if not fvalue then
726 self.tag_invalid[section] = true
727 end
728 if fvalue and not (fvalue == cvalue) then
729 self:write(section, fvalue)
730 end
731 else -- Unset the UCI or error
732 if self.rmempty or self.optional then
733 self:remove(section)
734 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
735 self.tag_missing[section] = true
736 end
737 end
738 end
739
740 -- Render if this value exists or if it is mandatory
741 function AbstractValue.render(self, s, scope)
742 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
743 scope = scope or {}
744 scope.section = s
745 scope.cbid = "cbid." .. self.config ..
746 "." .. s ..
747 "." .. self.option
748
749 scope.ifattr = function(cond,key,val)
750 if cond then
751 return string.format(
752 ' %s="%s"', tostring(key),
753 luci.util.pcdata(tostring( val
754 or scope[key]
755 or (type(self[key]) ~= "function" and self[key])
756 or "" ))
757 )
758 else
759 return ''
760 end
761 end
762
763 scope.attr = function(...)
764 return scope.ifattr( true, ... )
765 end
766
767 Node.render(self, scope)
768 end
769 end
770
771 -- Return the UCI value of this object
772 function AbstractValue.cfgvalue(self, section)
773 return self.stateful
774 and self.map:stateget(section, self.option)
775 or self.map:get(section, self.option)
776 end
777
778 -- Validate the form value
779 function AbstractValue.validate(self, value)
780 return value
781 end
782
783 AbstractValue.transform = AbstractValue.validate
784
785
786 -- Write to UCI
787 function AbstractValue.write(self, section, value)
788 return self.map:set(section, self.option, value)
789 end
790
791 -- Remove from UCI
792 function AbstractValue.remove(self, section)
793 return self.map:del(section, self.option)
794 end
795
796
797
798
799 --[[
800 Value - A one-line value
801 maxlength: The maximum length
802 ]]--
803 Value = class(AbstractValue)
804
805 function Value.__init__(self, ...)
806 AbstractValue.__init__(self, ...)
807 self.template = "cbi/value"
808 self.keylist = {}
809 self.vallist = {}
810 end
811
812 function Value.value(self, key, val)
813 val = val or key
814 table.insert(self.keylist, tostring(key))
815 table.insert(self.vallist, tostring(val))
816 end
817
818
819 -- DummyValue - This does nothing except being there
820 DummyValue = class(AbstractValue)
821
822 function DummyValue.__init__(self, map, ...)
823 AbstractValue.__init__(self, map, ...)
824 self.template = "cbi/dvalue"
825 self.value = nil
826 end
827
828 function DummyValue.parse(self)
829
830 end
831
832
833 --[[
834 Flag - A flag being enabled or disabled
835 ]]--
836 Flag = class(AbstractValue)
837
838 function Flag.__init__(self, ...)
839 AbstractValue.__init__(self, ...)
840 self.template = "cbi/fvalue"
841
842 self.enabled = "1"
843 self.disabled = "0"
844 end
845
846 -- A flag can only have two states: set or unset
847 function Flag.parse(self, section)
848 local fvalue = self:formvalue(section)
849
850 if fvalue then
851 fvalue = self.enabled
852 else
853 fvalue = self.disabled
854 end
855
856 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
857 if not(fvalue == self:cfgvalue(section)) then
858 self:write(section, fvalue)
859 end
860 else
861 self:remove(section)
862 end
863 end
864
865
866
867 --[[
868 ListValue - A one-line value predefined in a list
869 widget: The widget that will be used (select, radio)
870 ]]--
871 ListValue = class(AbstractValue)
872
873 function ListValue.__init__(self, ...)
874 AbstractValue.__init__(self, ...)
875 self.template = "cbi/lvalue"
876 self.keylist = {}
877 self.vallist = {}
878
879 self.size = 1
880 self.widget = "select"
881 end
882
883 function ListValue.value(self, key, val)
884 val = val or key
885 table.insert(self.keylist, tostring(key))
886 table.insert(self.vallist, tostring(val))
887 end
888
889 function ListValue.validate(self, val)
890 if luci.util.contains(self.keylist, val) then
891 return val
892 else
893 return nil
894 end
895 end
896
897
898
899 --[[
900 MultiValue - Multiple delimited values
901 widget: The widget that will be used (select, checkbox)
902 delimiter: The delimiter that will separate the values (default: " ")
903 ]]--
904 MultiValue = class(AbstractValue)
905
906 function MultiValue.__init__(self, ...)
907 AbstractValue.__init__(self, ...)
908 self.template = "cbi/mvalue"
909 self.keylist = {}
910 self.vallist = {}
911
912 self.widget = "checkbox"
913 self.delimiter = " "
914 end
915
916 function MultiValue.render(self, ...)
917 if self.widget == "select" and not self.size then
918 self.size = #self.vallist
919 end
920
921 AbstractValue.render(self, ...)
922 end
923
924 function MultiValue.value(self, key, val)
925 val = val or key
926 table.insert(self.keylist, tostring(key))
927 table.insert(self.vallist, tostring(val))
928 end
929
930 function MultiValue.valuelist(self, section)
931 local val = self:cfgvalue(section)
932
933 if not(type(val) == "string") then
934 return {}
935 end
936
937 return luci.util.split(val, self.delimiter)
938 end
939
940 function MultiValue.validate(self, val)
941 val = (type(val) == "table") and val or {val}
942
943 local result
944
945 for i, value in ipairs(val) do
946 if luci.util.contains(self.keylist, value) then
947 result = result and (result .. self.delimiter .. value) or value
948 end
949 end
950
951 return result
952 end
953
954 --[[
955 TextValue - A multi-line value
956 rows: Rows
957 ]]--
958 TextValue = class(AbstractValue)
959
960 function TextValue.__init__(self, ...)
961 AbstractValue.__init__(self, ...)
962 self.template = "cbi/tvalue"
963 end