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