libs/cbi: Added "Save & Apply" button to CBI forms
[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
39 -- Loads a CBI map from given file, creating an environment and returns it
40 function load(cbimap)
41         require("luci.fs")
42         require("luci.i18n")
43         require("luci.config")
44         require("luci.sys")
45
46         local cbidir = luci.sys.libpath() .. "/model/cbi/"
47         local func, err = loadfile(cbidir..cbimap..".lua")
48
49         if not func then
50                 return nil
51         end
52
53         luci.i18n.loadc("cbi")
54
55         luci.util.resfenv(func)
56         luci.util.updfenv(func, luci.cbi)
57         luci.util.extfenv(func, "translate", luci.i18n.translate)
58         luci.util.extfenv(func, "translatef", luci.i18n.translatef)
59
60         local maps = {func()}
61
62         for i, map in ipairs(maps) do
63                 if not instanceof(map, Map) then
64                         error("CBI map returns no valid map object!")
65                         return nil
66                 end
67         end
68
69         return maps
70 end
71
72 -- Node pseudo abstract class
73 Node = class()
74
75 function Node.__init__(self, title, description)
76         self.children = {}
77         self.title = title or ""
78         self.description = description or ""
79         self.template = "cbi/node"
80 end
81
82 -- i18n helper
83 function Node._i18n(self, config, section, option, title, description)
84
85         -- i18n loaded?
86         if type(luci.i18n) == "table" then
87
88                 local key = config:gsub("[^%w]+", "")
89
90                 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
91                 if option  then key = key .. "_" .. option:lower():gsub("[^%w]+", "")  end
92
93                 self.title = title or luci.i18n.translate( key, option or section or config )
94                 self.description = description or luci.i18n.translate( key .. "_desc", "" )
95         end
96 end
97
98 -- Append child nodes
99 function Node.append(self, obj)
100         table.insert(self.children, obj)
101 end
102
103 -- Parse this node and its children
104 function Node.parse(self, ...)
105         for k, child in ipairs(self.children) do
106                 child:parse(...)
107         end
108 end
109
110 -- Render this node
111 function Node.render(self, scope)
112         scope = scope or {}
113         scope.self = self
114
115         luci.template.render(self.template, scope)
116 end
117
118 -- Render the children
119 function Node.render_children(self, ...)
120         for k, node in ipairs(self.children) do
121                 node:render(...)
122         end
123 end
124
125
126 --[[
127 A simple template element
128 ]]--
129 Template = class(Node)
130
131 function Template.__init__(self, template)
132         Node.__init__(self)
133         self.template = template
134 end
135
136
137 --[[
138 Map - A map describing a configuration file
139 ]]--
140 Map = class(Node)
141
142 function Map.__init__(self, config, ...)
143         Node.__init__(self, ...)
144         Node._i18n(self, config, nil, nil, ...)
145
146         self.config = config
147         self.template = "cbi/map"
148         if not uci.load(self.config) then
149                 error("Unable to read UCI data: " .. self.config)
150         end
151 end
152
153 -- Use optimized UCI writing
154 function Map.parse(self, ...)
155         Node.parse(self, ...)
156         uci.save(self.config)
157         if luci.http.formvalue("cbi.apply") then
158                 uci.commit(self.config)
159         end
160         uci.unload(self.config)
161 end
162
163 -- Creates a child section
164 function Map.section(self, class, ...)
165         if instanceof(class, AbstractSection) then
166                 local obj  = class(self, ...)
167                 self:append(obj)
168                 return obj
169         else
170                 error("class must be a descendent of AbstractSection")
171         end
172 end
173
174 -- UCI add
175 function Map.add(self, sectiontype)
176         return uci.add(self.config, sectiontype)
177 end
178
179 -- UCI set
180 function Map.set(self, section, option, value)
181         if option then
182                 return uci.set(self.config, section, option, value)
183         else
184                 return uci.set(self.config, section, value)
185         end
186 end
187
188 -- UCI del
189 function Map.del(self, section, option)
190         if option then
191                 return uci.delete(self.config, section, option)
192         else
193                 return uci.delete(self.config, section)
194         end
195 end
196
197 -- UCI get
198 function Map.get(self, section, option)
199         if not section then
200                 return uci.get_all(self.config)
201         elseif option then
202                 return uci.get(self.config, section, option)
203         else
204                 return uci.get_all(self.config, section)
205         end
206 end
207
208
209 --[[
210 AbstractSection
211 ]]--
212 AbstractSection = class(Node)
213
214 function AbstractSection.__init__(self, map, sectiontype, ...)
215         Node.__init__(self, ...)
216         self.sectiontype = sectiontype
217         self.map = map
218         self.config = map.config
219         self.optionals = {}
220         self.defaults = {}
221
222         self.optional = true
223         self.addremove = false
224         self.dynamic = false
225 end
226
227 -- Appends a new option
228 function AbstractSection.option(self, class, option, ...)
229         if instanceof(class, AbstractValue) then
230                 local obj  = class(self.map, option, ...)
231
232                 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
233
234                 self:append(obj)
235                 return obj
236         else
237                 error("class must be a descendent of AbstractValue")
238         end
239 end
240
241 -- Parse optional options
242 function AbstractSection.parse_optionals(self, section)
243         if not self.optional then
244                 return
245         end
246
247         self.optionals[section] = {}
248
249         local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
250         for k,v in ipairs(self.children) do
251                 if v.optional and not v:cfgvalue(section) then
252                         if field == v.option then
253                                 field = nil
254                         else
255                                 table.insert(self.optionals[section], v)
256                         end
257                 end
258         end
259
260         if field and #field > 0 and self.dynamic then
261                 self:add_dynamic(field)
262         end
263 end
264
265 -- Add a dynamic option
266 function AbstractSection.add_dynamic(self, field, optional)
267         local o = self:option(Value, field, field)
268         o.optional = optional
269 end
270
271 -- Parse all dynamic options
272 function AbstractSection.parse_dynamic(self, section)
273         if not self.dynamic then
274                 return
275         end
276
277         local arr  = luci.util.clone(self:cfgvalue(section))
278         local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
279         for k, v in pairs(form) do
280                 arr[k] = v
281         end
282
283         for key,val in pairs(arr) do
284                 local create = true
285
286                 for i,c in ipairs(self.children) do
287                         if c.option == key then
288                                 create = false
289                         end
290                 end
291
292                 if create and key:sub(1, 1) ~= "." then
293                         self:add_dynamic(key, true)
294                 end
295         end
296 end
297
298 -- Returns the section's UCI table
299 function AbstractSection.cfgvalue(self, section)
300         return self.map:get(section)
301 end
302
303 -- Removes the section
304 function AbstractSection.remove(self, section)
305         return self.map:del(section)
306 end
307
308 -- Creates the section
309 function AbstractSection.create(self, section)
310         local stat
311         
312         if section then
313                 stat = self.map:set(section, nil, self.sectiontype)
314         else
315                 section = self.map:add(self.sectiontype)
316                 stat = section
317         end
318
319         if stat then
320                 for k,v in pairs(self.children) do
321                         if v.default then
322                                 self.map:set(section, v.option, v.default)
323                         end
324                 end
325
326                 for k,v in pairs(self.defaults) do
327                         self.map:set(section, k, v)
328                 end
329         end
330
331         return stat
332 end
333
334
335
336 --[[
337 NamedSection - A fixed configuration section defined by its name
338 ]]--
339 NamedSection = class(AbstractSection)
340
341 function NamedSection.__init__(self, map, section, type, ...)
342         AbstractSection.__init__(self, map, type, ...)
343         Node._i18n(self, map.config, section, nil, ...)
344
345         self.template = "cbi/nsection"
346         self.section = section
347         self.addremove = false
348 end
349
350 function NamedSection.parse(self)
351         local s = self.section
352         local active = self:cfgvalue(s)
353
354
355         if self.addremove then
356                 local path = self.config.."."..s
357                 if active then -- Remove the section
358                         if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
359                                 return
360                         end
361                 else           -- Create and apply default values
362                         if luci.http.formvalue("cbi.cns."..path) then
363                                 self:create(s)
364                                 return
365                         end
366                 end
367         end
368
369         if active then
370                 AbstractSection.parse_dynamic(self, s)
371                 if luci.http.formvalue("cbi.submit") then
372                         Node.parse(self, s)
373                 end
374                 AbstractSection.parse_optionals(self, s)
375         end
376 end
377
378
379 --[[
380 TypedSection - A (set of) configuration section(s) defined by the type
381         addremove:      Defines whether the user can add/remove sections of this type
382         anonymous:  Allow creating anonymous sections
383         validate:       a validation function returning nil if the section is invalid
384 ]]--
385 TypedSection = class(AbstractSection)
386
387 function TypedSection.__init__(self, map, type, ...)
388         AbstractSection.__init__(self, map, type, ...)
389         Node._i18n(self, map.config, type, nil, ...)
390
391         self.template  = "cbi/tsection"
392         self.deps = {}
393         self.excludes = {}
394
395         self.anonymous = false
396 end
397
398 -- Return all matching UCI sections for this TypedSection
399 function TypedSection.cfgsections(self)
400         local sections = {}
401         uci.foreach(self.map.config, self.sectiontype,
402                 function (section)
403                         if self:checkscope(section[".name"]) then
404                                 table.insert(sections, section[".name"])
405                         end
406                 end)
407
408         return sections
409 end
410
411 -- Limits scope to sections that have certain option => value pairs
412 function TypedSection.depends(self, option, value)
413         table.insert(self.deps, {option=option, value=value})
414 end
415
416 -- Excludes several sections by name
417 function TypedSection.exclude(self, field)
418         self.excludes[field] = true
419 end
420
421 function TypedSection.parse(self)
422         if self.addremove then
423                 -- Create
424                 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
425                 local name  = luci.http.formvalue(crval)
426                 if self.anonymous then
427                         if name then
428                                 self:create()
429                         end
430                 else
431                         if name then
432                                 -- Ignore if it already exists
433                                 if self:cfgvalue(name) then
434                                         name = nil;
435                                 end
436
437                                 name = self:checkscope(name)
438
439                                 if not name then
440                                         self.err_invalid = true
441                                 end
442
443                                 if name and name:len() > 0 then
444                                         self:create(name)
445                                 end
446                         end
447                 end
448
449                 -- Remove
450                 crval = "cbi.rts." .. self.config
451                 name = luci.http.formvaluetable(crval)
452                 for k,v in pairs(name) do
453                         if self:cfgvalue(k) and self:checkscope(k) then
454                                 self:remove(k)
455                         end
456                 end
457         end
458
459         for i, k in ipairs(self:cfgsections()) do
460                 AbstractSection.parse_dynamic(self, k)
461                 if luci.http.formvalue("cbi.submit") then
462                         Node.parse(self, k)
463                 end
464                 AbstractSection.parse_optionals(self, k)
465         end
466 end
467
468 -- Verifies scope of sections
469 function TypedSection.checkscope(self, section)
470         -- Check if we are not excluded
471         if self.excludes[section] then
472                 return nil
473         end
474
475         -- Check if at least one dependency is met
476         if #self.deps > 0 and self:cfgvalue(section) then
477                 local stat = false
478
479                 for k, v in ipairs(self.deps) do
480                         if self:cfgvalue(section)[v.option] == v.value then
481                                 stat = true
482                         end
483                 end
484
485                 if not stat then
486                         return nil
487                 end
488         end
489
490         return self:validate(section)
491 end
492
493
494 -- Dummy validate function
495 function TypedSection.validate(self, section)
496         return section
497 end
498
499
500 --[[
501 AbstractValue - An abstract Value Type
502         null:           Value can be empty
503         valid:          A function returning the value if it is valid otherwise nil
504         depends:        A table of option => value pairs of which one must be true
505         default:        The default value
506         size:           The size of the input fields
507         rmempty:        Unset value if empty
508         optional:       This value is optional (see AbstractSection.optionals)
509 ]]--
510 AbstractValue = class(Node)
511
512 function AbstractValue.__init__(self, map, option, ...)
513         Node.__init__(self, ...)
514         self.option = option
515         self.map    = map
516         self.config = map.config
517         self.tag_invalid = {}
518         self.deps = {}
519
520         self.rmempty  = false
521         self.default  = nil
522         self.size     = nil
523         self.optional = false
524 end
525
526 -- Add a dependencie to another section field
527 function AbstractValue.depends(self, field, value)
528         table.insert(self.deps, {field=field, value=value})
529 end
530
531 -- Return whether this object should be created
532 function AbstractValue.formcreated(self, section)
533         local key = "cbi.opt."..self.config.."."..section
534         return (luci.http.formvalue(key) == self.option)
535 end
536
537 -- Returns the formvalue for this object
538 function AbstractValue.formvalue(self, section)
539         local key = "cbid."..self.map.config.."."..section.."."..self.option
540         return luci.http.formvalue(key)
541 end
542
543 function AbstractValue.parse(self, section)
544         local fvalue = self:formvalue(section)
545
546         if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
547                 fvalue = self:validate(fvalue)
548                 if not fvalue then
549                         self.tag_invalid[section] = true
550                 end
551                 if fvalue and not (fvalue == self:cfgvalue(section)) then
552                         self:write(section, fvalue)
553                 end
554         else                                                    -- Unset the UCI or error
555                 if self.rmempty or self.optional then
556                         self:remove(section)
557                 end
558         end
559 end
560
561 -- Render if this value exists or if it is mandatory
562 function AbstractValue.render(self, s, scope)
563         if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
564                 scope = scope or {}
565                 scope.section = s
566                 scope.cbid    = "cbid." .. self.config ..
567                                 "."     .. s           ..
568                                                 "."     .. self.option
569
570                 scope.ifattr = function(cond,key,val)
571                         if cond then
572                                 return string.format(
573                                         ' %s="%s"', tostring(key),
574                                         tostring( val or scope[key] or self[key] or "" )
575                                 )
576                         else
577                                 return ''
578                         end
579                 end
580
581                 scope.attr = function(...)
582                         return scope.ifattr( true, ... )
583                 end
584
585                 Node.render(self, scope)
586         end
587 end
588
589 -- Return the UCI value of this object
590 function AbstractValue.cfgvalue(self, section)
591         return self.map:get(section, self.option)
592 end
593
594 -- Validate the form value
595 function AbstractValue.validate(self, value)
596         return value
597 end
598
599 -- Write to UCI
600 function AbstractValue.write(self, section, value)
601         return self.map:set(section, self.option, value)
602 end
603
604 -- Remove from UCI
605 function AbstractValue.remove(self, section)
606         return self.map:del(section, self.option)
607 end
608
609
610
611
612 --[[
613 Value - A one-line value
614         maxlength:      The maximum length
615 ]]--
616 Value = class(AbstractValue)
617
618 function Value.__init__(self, ...)
619         AbstractValue.__init__(self, ...)
620         self.template  = "cbi/value"
621
622         self.maxlength  = nil
623 end
624
625 -- This validation is a bit more complex
626 function Value.validate(self, val)
627         if self.maxlength and tostring(val):len() > self.maxlength then
628                 val = nil
629         end
630
631         return val
632 end
633
634
635 -- DummyValue - This does nothing except being there
636 DummyValue = class(AbstractValue)
637
638 function DummyValue.__init__(self, map, ...)
639         AbstractValue.__init__(self, map, ...)
640         self.template = "cbi/dvalue"
641         self.value = nil
642 end
643
644 function DummyValue.parse(self)
645
646 end
647
648
649 --[[
650 Flag - A flag being enabled or disabled
651 ]]--
652 Flag = class(AbstractValue)
653
654 function Flag.__init__(self, ...)
655         AbstractValue.__init__(self, ...)
656         self.template  = "cbi/fvalue"
657
658         self.enabled = "1"
659         self.disabled = "0"
660 end
661
662 -- A flag can only have two states: set or unset
663 function Flag.parse(self, section)
664         local fvalue = self:formvalue(section)
665
666         if fvalue then
667                 fvalue = self.enabled
668         else
669                 fvalue = self.disabled
670         end
671
672         if fvalue == self.enabled or (not self.optional and not self.rmempty) then
673                 if not(fvalue == self:cfgvalue(section)) then
674                         self:write(section, fvalue)
675                 end
676         else
677                 self:remove(section)
678         end
679 end
680
681
682
683 --[[
684 ListValue - A one-line value predefined in a list
685         widget: The widget that will be used (select, radio)
686 ]]--
687 ListValue = class(AbstractValue)
688
689 function ListValue.__init__(self, ...)
690         AbstractValue.__init__(self, ...)
691         self.template  = "cbi/lvalue"
692         self.keylist = {}
693         self.vallist = {}
694
695         self.size   = 1
696         self.widget = "select"
697 end
698
699 function ListValue.value(self, key, val)
700         val = val or key
701         table.insert(self.keylist, tostring(key))
702         table.insert(self.vallist, tostring(val))
703 end
704
705 function ListValue.validate(self, val)
706         if luci.util.contains(self.keylist, val) then
707                 return val
708         else
709                 return nil
710         end
711 end
712
713
714
715 --[[
716 MultiValue - Multiple delimited values
717         widget: The widget that will be used (select, checkbox)
718         delimiter: The delimiter that will separate the values (default: " ")
719 ]]--
720 MultiValue = class(AbstractValue)
721
722 function MultiValue.__init__(self, ...)
723         AbstractValue.__init__(self, ...)
724         self.template = "cbi/mvalue"
725         self.keylist = {}
726         self.vallist = {}
727
728         self.widget = "checkbox"
729         self.delimiter = " "
730 end
731
732 function MultiValue.render(self, ...)
733         if self.widget == "select" and not self.size then
734                 self.size = #self.vallist
735         end
736
737         AbstractValue.render(self, ...)
738 end
739
740 function MultiValue.value(self, key, val)
741         val = val or key
742         table.insert(self.keylist, tostring(key))
743         table.insert(self.vallist, tostring(val))
744 end
745
746 function MultiValue.valuelist(self, section)
747         local val = self:cfgvalue(section)
748
749         if not(type(val) == "string") then
750                 return {}
751         end
752
753         return luci.util.split(val, self.delimiter)
754 end
755
756 function MultiValue.validate(self, val)
757         val = (type(val) == "table") and val or {val}
758
759         local result
760
761         for i, value in ipairs(val) do
762                 if luci.util.contains(self.keylist, value) then
763                         result = result and (result .. self.delimiter .. value) or value
764                 end
765         end
766
767         return result
768 end