3bb56f36cac517b5a0f93975143be38fc2875d2b
[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 SimpleForm - A Simple non-UCI form
251 ]]--
252 SimpleForm = class(Node)
253
254 function SimpleForm.__init__(self, config, title, description, data)
255 Node.__init__(self, title, description)
256 self.config = config
257 self.data = data or {}
258 self.template = "cbi/simpleform"
259 self.dorender = true
260 end
261
262 function SimpleForm.parse(self, ...)
263 Node.parse(self, 1, ...)
264
265 local valid = true
266 for i, v in ipairs(self.children) do
267 valid = valid
268 and (not v.tag_missing or not v.tag_missing[1])
269 and (not v.tag_invalid or not v.tag_invalid[1])
270 end
271
272 local state =
273 not luci.http.formvalue("cbi.submit") and 0
274 or valid and 1
275 or -1
276
277 self.dorender = self:handle(state, self.data)
278 end
279
280 function SimpleForm.render(self, ...)
281 if self.dorender then
282 Node.render(self, ...)
283 end
284 end
285
286 -- Creates a child section
287 function SimpleForm.field(self, class, ...)
288 if instanceof(class, AbstractValue) then
289 local obj = class(self, ...)
290 obj.track_missing = true
291 self:append(obj)
292 return obj
293 else
294 error("class must be a descendent of AbstractValue")
295 end
296 end
297
298 function SimpleForm.set(self, section, option, value)
299 self.data[option] = value
300 end
301
302
303 function SimpleForm.del(self, section, option)
304 self.data[option] = nil
305 end
306
307
308 function SimpleForm.get(self, section, option)
309 return self.data[option]
310 end
311
312
313
314 --[[
315 AbstractSection
316 ]]--
317 AbstractSection = class(Node)
318
319 function AbstractSection.__init__(self, map, sectiontype, ...)
320 Node.__init__(self, ...)
321 self.sectiontype = sectiontype
322 self.map = map
323 self.config = map.config
324 self.optionals = {}
325 self.defaults = {}
326
327 self.optional = true
328 self.addremove = false
329 self.dynamic = false
330 end
331
332 -- Appends a new option
333 function AbstractSection.option(self, class, option, ...)
334 if instanceof(class, AbstractValue) then
335 local obj = class(self.map, option, ...)
336
337 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
338
339 self:append(obj)
340 return obj
341 else
342 error("class must be a descendent of AbstractValue")
343 end
344 end
345
346 -- Parse optional options
347 function AbstractSection.parse_optionals(self, section)
348 if not self.optional then
349 return
350 end
351
352 self.optionals[section] = {}
353
354 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
355 for k,v in ipairs(self.children) do
356 if v.optional and not v:cfgvalue(section) then
357 if field == v.option then
358 field = nil
359 else
360 table.insert(self.optionals[section], v)
361 end
362 end
363 end
364
365 if field and #field > 0 and self.dynamic then
366 self:add_dynamic(field)
367 end
368 end
369
370 -- Add a dynamic option
371 function AbstractSection.add_dynamic(self, field, optional)
372 local o = self:option(Value, field, field)
373 o.optional = optional
374 end
375
376 -- Parse all dynamic options
377 function AbstractSection.parse_dynamic(self, section)
378 if not self.dynamic then
379 return
380 end
381
382 local arr = luci.util.clone(self:cfgvalue(section))
383 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
384 for k, v in pairs(form) do
385 arr[k] = v
386 end
387
388 for key,val in pairs(arr) do
389 local create = true
390
391 for i,c in ipairs(self.children) do
392 if c.option == key then
393 create = false
394 end
395 end
396
397 if create and key:sub(1, 1) ~= "." then
398 self:add_dynamic(key, true)
399 end
400 end
401 end
402
403 -- Returns the section's UCI table
404 function AbstractSection.cfgvalue(self, section)
405 return self.map:get(section)
406 end
407
408 -- Removes the section
409 function AbstractSection.remove(self, section)
410 return self.map:del(section)
411 end
412
413 -- Creates the section
414 function AbstractSection.create(self, section)
415 local stat
416
417 if section then
418 stat = self.map:set(section, nil, self.sectiontype)
419 else
420 section = self.map:add(self.sectiontype)
421 stat = section
422 end
423
424 if stat then
425 for k,v in pairs(self.children) do
426 if v.default then
427 self.map:set(section, v.option, v.default)
428 end
429 end
430
431 for k,v in pairs(self.defaults) do
432 self.map:set(section, k, v)
433 end
434 end
435
436 return stat
437 end
438
439
440
441 --[[
442 NamedSection - A fixed configuration section defined by its name
443 ]]--
444 NamedSection = class(AbstractSection)
445
446 function NamedSection.__init__(self, map, section, type, ...)
447 AbstractSection.__init__(self, map, type, ...)
448 Node._i18n(self, map.config, section, nil, ...)
449
450 self.template = "cbi/nsection"
451 self.section = section
452 self.addremove = false
453 end
454
455 function NamedSection.parse(self)
456 local s = self.section
457 local active = self:cfgvalue(s)
458
459
460 if self.addremove then
461 local path = self.config.."."..s
462 if active then -- Remove the section
463 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
464 return
465 end
466 else -- Create and apply default values
467 if luci.http.formvalue("cbi.cns."..path) then
468 self:create(s)
469 return
470 end
471 end
472 end
473
474 if active then
475 AbstractSection.parse_dynamic(self, s)
476 if luci.http.formvalue("cbi.submit") then
477 Node.parse(self, s)
478 end
479 AbstractSection.parse_optionals(self, s)
480 end
481 end
482
483
484 --[[
485 TypedSection - A (set of) configuration section(s) defined by the type
486 addremove: Defines whether the user can add/remove sections of this type
487 anonymous: Allow creating anonymous sections
488 validate: a validation function returning nil if the section is invalid
489 ]]--
490 TypedSection = class(AbstractSection)
491
492 function TypedSection.__init__(self, map, type, ...)
493 AbstractSection.__init__(self, map, type, ...)
494 Node._i18n(self, map.config, type, nil, ...)
495
496 self.template = "cbi/tsection"
497 self.deps = {}
498
499 self.anonymous = false
500 end
501
502 -- Return all matching UCI sections for this TypedSection
503 function TypedSection.cfgsections(self)
504 local sections = {}
505 uci.foreach(self.map.config, self.sectiontype,
506 function (section)
507 if self:checkscope(section[".name"]) then
508 table.insert(sections, section[".name"])
509 end
510 end)
511
512 return sections
513 end
514
515 -- Limits scope to sections that have certain option => value pairs
516 function TypedSection.depends(self, option, value)
517 table.insert(self.deps, {option=option, value=value})
518 end
519
520 function TypedSection.parse(self)
521 if self.addremove then
522 -- Create
523 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
524 local name = luci.http.formvalue(crval)
525 if self.anonymous then
526 if name then
527 self:create()
528 end
529 else
530 if name then
531 -- Ignore if it already exists
532 if self:cfgvalue(name) then
533 name = nil;
534 end
535
536 name = self:checkscope(name)
537
538 if not name then
539 self.err_invalid = true
540 end
541
542 if name and name:len() > 0 then
543 self:create(name)
544 end
545 end
546 end
547
548 -- Remove
549 crval = REMOVE_PREFIX .. self.config
550 name = luci.http.formvaluetable(crval)
551 for k,v in pairs(name) do
552 if self:cfgvalue(k) and self:checkscope(k) then
553 self:remove(k)
554 end
555 end
556 end
557
558 for i, k in ipairs(self:cfgsections()) do
559 AbstractSection.parse_dynamic(self, k)
560 if luci.http.formvalue("cbi.submit") then
561 Node.parse(self, k)
562 end
563 AbstractSection.parse_optionals(self, k)
564 end
565 end
566
567 -- Verifies scope of sections
568 function TypedSection.checkscope(self, section)
569 -- Check if we are not excluded
570 if self.filter and not self:filter(section) then
571 return nil
572 end
573
574 -- Check if at least one dependency is met
575 if #self.deps > 0 and self:cfgvalue(section) then
576 local stat = false
577
578 for k, v in ipairs(self.deps) do
579 if self:cfgvalue(section)[v.option] == v.value then
580 stat = true
581 end
582 end
583
584 if not stat then
585 return nil
586 end
587 end
588
589 return self:validate(section)
590 end
591
592
593 -- Dummy validate function
594 function TypedSection.validate(self, section)
595 return section
596 end
597
598
599 --[[
600 AbstractValue - An abstract Value Type
601 null: Value can be empty
602 valid: A function returning the value if it is valid otherwise nil
603 depends: A table of option => value pairs of which one must be true
604 default: The default value
605 size: The size of the input fields
606 rmempty: Unset value if empty
607 optional: This value is optional (see AbstractSection.optionals)
608 ]]--
609 AbstractValue = class(Node)
610
611 function AbstractValue.__init__(self, map, option, ...)
612 Node.__init__(self, ...)
613 self.option = option
614 self.map = map
615 self.config = map.config
616 self.tag_invalid = {}
617 self.tag_missing = {}
618 self.deps = {}
619
620 self.track_missing = false
621 self.rmempty = false
622 self.default = nil
623 self.size = nil
624 self.optional = false
625 self.stateful = false
626 end
627
628 -- Add a dependencie to another section field
629 function AbstractValue.depends(self, field, value)
630 table.insert(self.deps, {field=field, value=value})
631 end
632
633 -- Return whether this object should be created
634 function AbstractValue.formcreated(self, section)
635 local key = "cbi.opt."..self.config.."."..section
636 return (luci.http.formvalue(key) == self.option)
637 end
638
639 -- Returns the formvalue for this object
640 function AbstractValue.formvalue(self, section)
641 local key = "cbid."..self.map.config.."."..section.."."..self.option
642 return luci.http.formvalue(key)
643 end
644
645 function AbstractValue.additional(self, value)
646 self.optional = value
647 end
648
649 function AbstractValue.mandatory(self, value)
650 self.rmempty = not value
651 end
652
653 function AbstractValue.parse(self, section)
654 local fvalue = self:formvalue(section)
655 local cvalue = self:cfgvalue(section)
656
657 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
658 fvalue = self:transform(self:validate(fvalue, section))
659 if not fvalue then
660 self.tag_invalid[section] = true
661 end
662 if fvalue and not (fvalue == cvalue) then
663 self:write(section, fvalue)
664 end
665 else -- Unset the UCI or error
666 if self.rmempty or self.optional then
667 self:remove(section)
668 elseif self.track_missing and not fvalue or fvalue ~= cvalue then
669 self.tag_missing[section] = true
670 end
671 end
672 end
673
674 -- Render if this value exists or if it is mandatory
675 function AbstractValue.render(self, s, scope)
676 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
677 scope = scope or {}
678 scope.section = s
679 scope.cbid = "cbid." .. self.config ..
680 "." .. s ..
681 "." .. self.option
682
683 scope.ifattr = function(cond,key,val)
684 if cond then
685 return string.format(
686 ' %s="%s"', tostring(key),
687 tostring( val
688 or scope[key]
689 or (type(self[key]) ~= "function" and self[key])
690 or "" )
691 )
692 else
693 return ''
694 end
695 end
696
697 scope.attr = function(...)
698 return scope.ifattr( true, ... )
699 end
700
701 Node.render(self, scope)
702 end
703 end
704
705 -- Return the UCI value of this object
706 function AbstractValue.cfgvalue(self, section)
707 return self.stateful
708 and self.map:stateget(section, self.option)
709 or self.map:get(section, self.option)
710 end
711
712 -- Validate the form value
713 function AbstractValue.validate(self, value)
714 return value
715 end
716
717 AbstractValue.transform = AbstractValue.validate
718
719
720 -- Write to UCI
721 function AbstractValue.write(self, section, value)
722 return self.map:set(section, self.option, value)
723 end
724
725 -- Remove from UCI
726 function AbstractValue.remove(self, section)
727 return self.map:del(section, self.option)
728 end
729
730
731
732
733 --[[
734 Value - A one-line value
735 maxlength: The maximum length
736 ]]--
737 Value = class(AbstractValue)
738
739 function Value.__init__(self, ...)
740 AbstractValue.__init__(self, ...)
741 self.template = "cbi/value"
742 self.keylist = {}
743 self.vallist = {}
744 end
745
746 function Value.value(self, key, val)
747 val = val or key
748 table.insert(self.keylist, tostring(key))
749 table.insert(self.vallist, tostring(val))
750 end
751
752
753 -- DummyValue - This does nothing except being there
754 DummyValue = class(AbstractValue)
755
756 function DummyValue.__init__(self, map, ...)
757 AbstractValue.__init__(self, map, ...)
758 self.template = "cbi/dvalue"
759 self.value = nil
760 end
761
762 function DummyValue.parse(self)
763
764 end
765
766
767 --[[
768 Flag - A flag being enabled or disabled
769 ]]--
770 Flag = class(AbstractValue)
771
772 function Flag.__init__(self, ...)
773 AbstractValue.__init__(self, ...)
774 self.template = "cbi/fvalue"
775
776 self.enabled = "1"
777 self.disabled = "0"
778 end
779
780 -- A flag can only have two states: set or unset
781 function Flag.parse(self, section)
782 local fvalue = self:formvalue(section)
783
784 if fvalue then
785 fvalue = self.enabled
786 else
787 fvalue = self.disabled
788 end
789
790 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
791 if not(fvalue == self:cfgvalue(section)) then
792 self:write(section, fvalue)
793 end
794 else
795 self:remove(section)
796 end
797 end
798
799
800
801 --[[
802 ListValue - A one-line value predefined in a list
803 widget: The widget that will be used (select, radio)
804 ]]--
805 ListValue = class(AbstractValue)
806
807 function ListValue.__init__(self, ...)
808 AbstractValue.__init__(self, ...)
809 self.template = "cbi/lvalue"
810 self.keylist = {}
811 self.vallist = {}
812
813 self.size = 1
814 self.widget = "select"
815 end
816
817 function ListValue.value(self, key, val)
818 val = val or key
819 table.insert(self.keylist, tostring(key))
820 table.insert(self.vallist, tostring(val))
821 end
822
823 function ListValue.validate(self, val)
824 if luci.util.contains(self.keylist, val) then
825 return val
826 else
827 return nil
828 end
829 end
830
831
832
833 --[[
834 MultiValue - Multiple delimited values
835 widget: The widget that will be used (select, checkbox)
836 delimiter: The delimiter that will separate the values (default: " ")
837 ]]--
838 MultiValue = class(AbstractValue)
839
840 function MultiValue.__init__(self, ...)
841 AbstractValue.__init__(self, ...)
842 self.template = "cbi/mvalue"
843 self.keylist = {}
844 self.vallist = {}
845
846 self.widget = "checkbox"
847 self.delimiter = " "
848 end
849
850 function MultiValue.render(self, ...)
851 if self.widget == "select" and not self.size then
852 self.size = #self.vallist
853 end
854
855 AbstractValue.render(self, ...)
856 end
857
858 function MultiValue.value(self, key, val)
859 val = val or key
860 table.insert(self.keylist, tostring(key))
861 table.insert(self.vallist, tostring(val))
862 end
863
864 function MultiValue.valuelist(self, section)
865 local val = self:cfgvalue(section)
866
867 if not(type(val) == "string") then
868 return {}
869 end
870
871 return luci.util.split(val, self.delimiter)
872 end
873
874 function MultiValue.validate(self, val)
875 val = (type(val) == "table") and val or {val}
876
877 local result
878
879 for i, value in ipairs(val) do
880 if luci.util.contains(self.keylist, value) then
881 result = result and (result .. self.delimiter .. value) or value
882 end
883 end
884
885 return result
886 end
887
888 --[[
889 TextValue - A multi-line value
890 rows: Rows
891 ]]--
892 TextValue = class(AbstractValue)
893
894 function TextValue.__init__(self, ...)
895 AbstractValue.__init__(self, ...)
896 self.template = "cbi/tvalue"
897 end