CBI: Cancelaction and event callbacks for Delegators
[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 local util = require("luci.util")
31 require("luci.http")
32 require("luci.uvl")
33
34
35 --local event = require "luci.sys.event"
36 local fs = require("nixio.fs")
37 local uci = require("luci.model.uci")
38 local class = util.class
39 local instanceof = util.instanceof
40
41 FORM_NODATA = 0
42 FORM_PROCEED = 0
43 FORM_VALID = 1
44 FORM_DONE = 1
45 FORM_INVALID = -1
46 FORM_CHANGED = 2
47 FORM_SKIP = 4
48
49 AUTO = true
50
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
53
54 -- Loads a CBI map from given file, creating an environment and returns it
55 function load(cbimap, ...)
56 local fs = require "nixio.fs"
57 local i18n = require "luci.i18n"
58 require("luci.config")
59 require("luci.util")
60
61 local upldir = "/lib/uci/upload/"
62 local cbidir = luci.util.libpath() .. "/model/cbi/"
63 local func, err
64
65 if fs.access(cbimap) then
66 func, err = loadfile(cbimap)
67 elseif fs.access(cbidir..cbimap..".lua") then
68 func, err = loadfile(cbidir..cbimap..".lua")
69 elseif fs.access(cbidir..cbimap..".lua.gz") then
70 func, err = loadfile(cbidir..cbimap..".lua.gz")
71 else
72 func, err = nil, "Model '" .. cbimap .. "' not found!"
73 end
74
75 assert(func, err)
76
77 luci.i18n.loadc("cbi")
78 luci.i18n.loadc("uvl")
79
80 local env = {
81 translate=i18n.translate,
82 translatef=i18n.translatef,
83 arg={...}
84 }
85
86 setfenv(func, setmetatable(env, {__index =
87 function(tbl, key)
88 return rawget(tbl, key) or _M[key] or _G[key]
89 end}))
90
91 local maps = { func() }
92 local uploads = { }
93 local has_upload = false
94
95 for i, map in ipairs(maps) do
96 if not instanceof(map, Node) then
97 error("CBI map returns no valid map object!")
98 return nil
99 else
100 map:prepare()
101 if map.upload_fields then
102 has_upload = true
103 for _, field in ipairs(map.upload_fields) do
104 uploads[
105 field.config .. '.' ..
106 field.section.sectiontype .. '.' ..
107 field.option
108 ] = true
109 end
110 end
111 end
112 end
113
114 if has_upload then
115 local uci = luci.model.uci.cursor()
116 local prm = luci.http.context.request.message.params
117 local fd, cbid
118
119 luci.http.setfilehandler(
120 function( field, chunk, eof )
121 if not field then return end
122 if field.name and not cbid then
123 local c, s, o = field.name:gmatch(
124 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
125 )()
126
127 if c and s and o then
128 local t = uci:get( c, s )
129 if t and uploads[c.."."..t.."."..o] then
130 local path = upldir .. field.name
131 fd = io.open(path, "w")
132 if fd then
133 cbid = field.name
134 prm[cbid] = path
135 end
136 end
137 end
138 end
139
140 if field.name == cbid and fd then
141 fd:write(chunk)
142 end
143
144 if eof and fd then
145 fd:close()
146 fd = nil
147 cbid = nil
148 end
149 end
150 )
151 end
152
153 return maps
154 end
155
156 local function _uvl_validate_section(node, name)
157 local co = node.map:get()
158
159 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
160 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
161
162 local function tag_fields(e)
163 if e.option and node.fields[e.option] then
164 if node.fields[e.option].error then
165 node.fields[e.option].error[name] = e
166 else
167 node.fields[e.option].error = { [name] = e }
168 end
169 elseif e.childs then
170 for _, c in ipairs(e.childs) do tag_fields(c) end
171 end
172 end
173
174 local function tag_section(e)
175 local s = { }
176 for _, c in ipairs(e.childs or { e }) do
177 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
178 table.insert( s, c.childs[1]:string() )
179 else
180 table.insert( s, c:string() )
181 end
182 end
183 if #s > 0 then
184 if node.error then
185 node.error[name] = s
186 else
187 node.error = { [name] = s }
188 end
189 end
190 end
191
192 local stat, err = node.map.validator:validate_section(node.config, name, co)
193 if err then
194 node.map.save = false
195 tag_fields(err)
196 tag_section(err)
197 end
198
199 end
200
201 local function _uvl_strip_remote_dependencies(deps)
202 local clean = {}
203
204 for k, v in pairs(deps) do
205 k = k:gsub("%$config%.%$section%.", "")
206 if k:match("^[%w_]+$") and type(v) == "string" then
207 clean[k] = v
208 end
209 end
210
211 return clean
212 end
213
214
215 -- Node pseudo abstract class
216 Node = class()
217
218 function Node.__init__(self, title, description)
219 self.children = {}
220 self.title = title or ""
221 self.description = description or ""
222 self.template = "cbi/node"
223 end
224
225 -- i18n helper
226 function Node._i18n(self, config, section, option, title, description)
227
228 -- i18n loaded?
229 if type(luci.i18n) == "table" then
230
231 local key = config and config:gsub("[^%w]+", "") or ""
232
233 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
234 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
235
236 self.title = title or luci.i18n.translate( key, option or section or config )
237 self.description = description or luci.i18n.translate( key .. "_desc", "" )
238 end
239 end
240
241 -- Prepare nodes
242 function Node.prepare(self, ...)
243 for k, child in ipairs(self.children) do
244 child:prepare(...)
245 end
246 end
247
248 -- Append child nodes
249 function Node.append(self, obj)
250 table.insert(self.children, obj)
251 end
252
253 -- Parse this node and its children
254 function Node.parse(self, ...)
255 for k, child in ipairs(self.children) do
256 child:parse(...)
257 end
258 end
259
260 -- Render this node
261 function Node.render(self, scope)
262 scope = scope or {}
263 scope.self = self
264
265 luci.template.render(self.template, scope)
266 end
267
268 -- Render the children
269 function Node.render_children(self, ...)
270 for k, node in ipairs(self.children) do
271 node:render(...)
272 end
273 end
274
275
276 --[[
277 A simple template element
278 ]]--
279 Template = class(Node)
280
281 function Template.__init__(self, template)
282 Node.__init__(self)
283 self.template = template
284 end
285
286 function Template.render(self)
287 luci.template.render(self.template, {self=self})
288 end
289
290 function Template.parse(self, readinput)
291 self.readinput = (readinput ~= false)
292 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
293 end
294
295
296 --[[
297 Map - A map describing a configuration file
298 ]]--
299 Map = class(Node)
300
301 function Map.__init__(self, config, ...)
302 Node.__init__(self, ...)
303 Node._i18n(self, config, nil, nil, ...)
304
305 self.config = config
306 self.parsechain = {self.config}
307 self.template = "cbi/map"
308 self.apply_on_parse = nil
309 self.readinput = true
310 self.proceed = false
311 self.flow = {}
312
313 self.uci = uci.cursor()
314 self.save = true
315
316 self.changed = false
317
318 if not self.uci:load(self.config) then
319 error("Unable to read UCI data: " .. self.config)
320 end
321
322 self.validator = luci.uvl.UVL()
323 self.scheme = self.validator:get_scheme(self.config)
324
325 end
326
327 function Map.formvalue(self, key)
328 return self.readinput and luci.http.formvalue(key)
329 end
330
331 function Map.formvaluetable(self, key)
332 return self.readinput and luci.http.formvaluetable(key) or {}
333 end
334
335 function Map.get_scheme(self, sectiontype, option)
336 if not option then
337 return self.scheme and self.scheme.sections[sectiontype]
338 else
339 return self.scheme and self.scheme.variables[sectiontype]
340 and self.scheme.variables[sectiontype][option]
341 end
342 end
343
344 function Map.submitstate(self)
345 return self:formvalue("cbi.submit")
346 end
347
348 -- Chain foreign config
349 function Map.chain(self, config)
350 table.insert(self.parsechain, config)
351 end
352
353 function Map.state_handler(self, state)
354 return state
355 end
356
357 -- Use optimized UCI writing
358 function Map.parse(self, readinput, ...)
359 self.readinput = (readinput ~= false)
360
361 if self:formvalue("cbi.skip") then
362 self.state = FORM_SKIP
363 return self:state_handler(self.state)
364 end
365
366 Node.parse(self, ...)
367
368 if self.save then
369 for i, config in ipairs(self.parsechain) do
370 self.uci:save(config)
371 end
372 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
373 for i, config in ipairs(self.parsechain) do
374 self.uci:commit(config)
375
376 -- Refresh data because commit changes section names
377 self.uci:load(config)
378 end
379 if self.apply_on_parse then
380 self.uci:apply(self.parsechain)
381 else
382 self._apply = function()
383 local cmd = self.uci:apply(self.parsechain, true)
384 return io.popen(cmd)
385 end
386 end
387
388 -- Reparse sections
389 Node.parse(self, true)
390
391 end
392 for i, config in ipairs(self.parsechain) do
393 self.uci:unload(config)
394 end
395 if type(self.commit_handler) == "function" then
396 self:commit_handler(self:submitstate())
397 end
398 end
399
400 if self:submitstate() then
401 if not self.save then
402 self.state = FORM_INVALID
403 elseif self.proceed then
404 self.state = FORM_PROCEED
405 else
406 self.state = self.changed and FORM_CHANGED or FORM_VALID
407 end
408 else
409 self.state = FORM_NODATA
410 end
411
412 return self:state_handler(self.state)
413 end
414
415 function Map.render(self, ...)
416 Node.render(self, ...)
417 if self._apply then
418 local fp = self._apply()
419 fp:read("*a")
420 fp:close()
421 end
422 end
423
424 -- Creates a child section
425 function Map.section(self, class, ...)
426 if instanceof(class, AbstractSection) then
427 local obj = class(self, ...)
428 self:append(obj)
429 return obj
430 else
431 error("class must be a descendent of AbstractSection")
432 end
433 end
434
435 -- UCI add
436 function Map.add(self, sectiontype)
437 return self.uci:add(self.config, sectiontype)
438 end
439
440 -- UCI set
441 function Map.set(self, section, option, value)
442 if option then
443 return self.uci:set(self.config, section, option, value)
444 else
445 return self.uci:set(self.config, section, value)
446 end
447 end
448
449 -- UCI del
450 function Map.del(self, section, option)
451 if option then
452 return self.uci:delete(self.config, section, option)
453 else
454 return self.uci:delete(self.config, section)
455 end
456 end
457
458 -- UCI get
459 function Map.get(self, section, option)
460 if not section then
461 return self.uci:get_all(self.config)
462 elseif option then
463 return self.uci:get(self.config, section, option)
464 else
465 return self.uci:get_all(self.config, section)
466 end
467 end
468
469 --[[
470 Compound - Container
471 ]]--
472 Compound = class(Node)
473
474 function Compound.__init__(self, ...)
475 Node.__init__(self)
476 self.template = "cbi/compound"
477 self.children = {...}
478 end
479
480 function Compound.populate_delegator(self, delegator)
481 for _, v in ipairs(self.children) do
482 v.delegator = delegator
483 end
484 end
485
486 function Compound.parse(self, ...)
487 local cstate, state = 0
488
489 for k, child in ipairs(self.children) do
490 cstate = child:parse(...)
491 state = (not state or cstate < state) and cstate or state
492 end
493
494 return state
495 end
496
497
498 --[[
499 Delegator - Node controller
500 ]]--
501 Delegator = class(Node)
502 function Delegator.__init__(self, ...)
503 Node.__init__(self, ...)
504 self.nodes = {}
505 self.defaultpath = {}
506 self.pageaction = false
507 self.readinput = true
508 self.allow_reset = false
509 self.allow_cancel = false
510 self.allow_back = false
511 self.allow_finish = false
512 self.template = "cbi/delegator"
513 end
514
515 function Delegator.set(self, name, node)
516 if type(node) == "table" and getmetatable(node) == nil then
517 node = Compound(unpack(node))
518 end
519 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
520 assert(not self.nodes[name], "Duplicate entry")
521
522 self.nodes[name] = node
523 end
524
525 function Delegator.add(self, name, node)
526 node = self:set(name, node)
527 self.defaultpath[#self.defaultpath+1] = name
528 end
529
530 function Delegator.insert_after(self, name, after)
531 local n = #self.chain
532 for k, v in ipairs(self.chain) do
533 if v == state then
534 n = k + 1
535 break
536 end
537 end
538 table.insert(self.chain, n, name)
539 end
540
541 function Delegator.set_route(self, ...)
542 local n, chain, route = 0, self.chain, {...}
543 for i = 1, #chain do
544 if chain[i] == self.current then
545 n = i
546 break
547 end
548 end
549 for i = 1, #route do
550 n = n + 1
551 chain[n] = route[i]
552 end
553 for i = n + 1, #chain do
554 chain[i] = nil
555 end
556 end
557
558 function Delegator.get(self, name)
559 return self.nodes[name]
560 end
561
562 function Delegator.parse(self, ...)
563 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
564 if self.on_cancel then
565 self:on_cancel()
566 return FORM_DONE
567 end
568 end
569
570 if self.on_init and not Map.formvalue(self, "cbi.delg.current") then
571 self:on_init()
572 end
573
574 local newcurrent
575 self.chain = self.chain or self:get_chain()
576 self.current = self.current or self:get_active()
577 self.active = self.active or self:get(self.current)
578 assert(self.active, "Invalid state")
579
580 local stat = FORM_DONE
581 if type(self.active) ~= "function" then
582 self.active:populate_delegator(self)
583 stat = self.active:parse()
584 else
585 self:active()
586 end
587
588 if stat > FORM_PROCEED then
589 if Map.formvalue(self, "cbi.delg.back") then
590 newcurrent = self:get_prev(self.current)
591 else
592 newcurrent = self:get_next(self.current)
593 end
594 elseif stat < FORM_PROCEED then
595 return stat
596 end
597
598
599 if not Map.formvalue(self, "cbi.submit") then
600 return FORM_NODATA
601 elseif not newcurrent or not self:get(newcurrent) then
602 if self.on_done then
603 self:on_done()
604 end
605 return FORM_DONE
606 else
607 self.current = newcurrent
608 self.active = self:get(self.current)
609 if type(self.active) ~= "function" then
610 self.active:parse(false)
611 return FROM_PROCEED
612 else
613 return self:parse(...)
614 end
615 end
616 end
617
618 function Delegator.get_next(self, state)
619 for k, v in ipairs(self.chain) do
620 if v == state then
621 return self.chain[k+1]
622 end
623 end
624 end
625
626 function Delegator.get_prev(self, state)
627 for k, v in ipairs(self.chain) do
628 if v == state then
629 return self.chain[k-1]
630 end
631 end
632 end
633
634 function Delegator.get_chain(self)
635 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
636 return type(x) == "table" and x or {x}
637 end
638
639 function Delegator.get_active(self)
640 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
641 end
642
643 --[[
644 Page - A simple node
645 ]]--
646
647 Page = class(Node)
648 Page.__init__ = Node.__init__
649 Page.parse = function() end
650
651
652 --[[
653 SimpleForm - A Simple non-UCI form
654 ]]--
655 SimpleForm = class(Node)
656
657 function SimpleForm.__init__(self, config, title, description, data)
658 Node.__init__(self, title, description)
659 self.config = config
660 self.data = data or {}
661 self.template = "cbi/simpleform"
662 self.dorender = true
663 self.pageaction = false
664 self.readinput = true
665 end
666
667 SimpleForm.formvalue = Map.formvalue
668 SimpleForm.formvaluetable = Map.formvaluetable
669
670 function SimpleForm.parse(self, readinput, ...)
671 self.readinput = (readinput ~= false)
672
673 if self:formvalue("cbi.skip") then
674 return FORM_SKIP
675 end
676
677 if self:submitstate() then
678 Node.parse(self, 1, ...)
679 end
680
681 local valid = true
682 for k, j in ipairs(self.children) do
683 for i, v in ipairs(j.children) do
684 valid = valid
685 and (not v.tag_missing or not v.tag_missing[1])
686 and (not v.tag_invalid or not v.tag_invalid[1])
687 and (not v.error)
688 end
689 end
690
691 local state =
692 not self:submitstate() and FORM_NODATA
693 or valid and FORM_VALID
694 or FORM_INVALID
695
696 self.dorender = not self.handle
697 if self.handle then
698 local nrender, nstate = self:handle(state, self.data)
699 self.dorender = self.dorender or (nrender ~= false)
700 state = nstate or state
701 end
702 return state
703 end
704
705 function SimpleForm.render(self, ...)
706 if self.dorender then
707 Node.render(self, ...)
708 end
709 end
710
711 function SimpleForm.submitstate(self)
712 return self:formvalue("cbi.submit")
713 end
714
715 function SimpleForm.section(self, class, ...)
716 if instanceof(class, AbstractSection) then
717 local obj = class(self, ...)
718 self:append(obj)
719 return obj
720 else
721 error("class must be a descendent of AbstractSection")
722 end
723 end
724
725 -- Creates a child field
726 function SimpleForm.field(self, class, ...)
727 local section
728 for k, v in ipairs(self.children) do
729 if instanceof(v, SimpleSection) then
730 section = v
731 break
732 end
733 end
734 if not section then
735 section = self:section(SimpleSection)
736 end
737
738 if instanceof(class, AbstractValue) then
739 local obj = class(self, section, ...)
740 obj.track_missing = true
741 section:append(obj)
742 return obj
743 else
744 error("class must be a descendent of AbstractValue")
745 end
746 end
747
748 function SimpleForm.set(self, section, option, value)
749 self.data[option] = value
750 end
751
752
753 function SimpleForm.del(self, section, option)
754 self.data[option] = nil
755 end
756
757
758 function SimpleForm.get(self, section, option)
759 return self.data[option]
760 end
761
762
763 function SimpleForm.get_scheme()
764 return nil
765 end
766
767
768 Form = class(SimpleForm)
769
770 function Form.__init__(self, ...)
771 SimpleForm.__init__(self, ...)
772 self.embedded = true
773 end
774
775
776 --[[
777 AbstractSection
778 ]]--
779 AbstractSection = class(Node)
780
781 function AbstractSection.__init__(self, map, sectiontype, ...)
782 Node.__init__(self, ...)
783 self.sectiontype = sectiontype
784 self.map = map
785 self.config = map.config
786 self.optionals = {}
787 self.defaults = {}
788 self.fields = {}
789 self.tag_error = {}
790 self.tag_invalid = {}
791 self.tag_deperror = {}
792 self.changed = false
793
794 self.optional = true
795 self.addremove = false
796 self.dynamic = false
797 end
798
799 -- Define a tab for the section
800 function AbstractSection.tab(self, tab, title, desc)
801 self.tabs = self.tabs or { }
802 self.tab_names = self.tab_names or { }
803
804 self.tab_names[#self.tab_names+1] = tab
805 self.tabs[tab] = {
806 title = title,
807 description = desc,
808 childs = { }
809 }
810 end
811
812 -- Appends a new option
813 function AbstractSection.option(self, class, option, ...)
814 -- Autodetect from UVL
815 if class == true and self.map:get_scheme(self.sectiontype, option) then
816 local vs = self.map:get_scheme(self.sectiontype, option)
817 if vs.type == "boolean" then
818 class = Flag
819 elseif vs.type == "list" then
820 class = DynamicList
821 elseif vs.type == "enum" or vs.type == "reference" then
822 class = ListValue
823 else
824 class = Value
825 end
826 end
827
828 if instanceof(class, AbstractValue) then
829 local obj = class(self.map, self, option, ...)
830
831 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
832
833 self:append(obj)
834 self.fields[option] = obj
835 return obj
836 elseif class == true then
837 error("No valid class was given and autodetection failed.")
838 else
839 error("class must be a descendant of AbstractValue")
840 end
841 end
842
843 -- Appends a new tabbed option
844 function AbstractSection.taboption(self, tab, ...)
845
846 assert(tab and self.tabs and self.tabs[tab],
847 "Cannot assign option to not existing tab %q" % tostring(tab))
848
849 local l = self.tabs[tab].childs
850 local o = AbstractSection.option(self, ...)
851
852 if o then l[#l+1] = o end
853
854 return o
855 end
856
857 -- Render a single tab
858 function AbstractSection.render_tab(self, tab, ...)
859
860 assert(tab and self.tabs and self.tabs[tab],
861 "Cannot render not existing tab %q" % tostring(tab))
862
863 for _, node in ipairs(self.tabs[tab].childs) do
864 node:render(...)
865 end
866 end
867
868 -- Parse optional options
869 function AbstractSection.parse_optionals(self, section)
870 if not self.optional then
871 return
872 end
873
874 self.optionals[section] = {}
875
876 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
877 for k,v in ipairs(self.children) do
878 if v.optional and not v:cfgvalue(section) then
879 if field == v.option then
880 field = nil
881 self.map.proceed = true
882 else
883 table.insert(self.optionals[section], v)
884 end
885 end
886 end
887
888 if field and #field > 0 and self.dynamic then
889 self:add_dynamic(field)
890 end
891 end
892
893 -- Add a dynamic option
894 function AbstractSection.add_dynamic(self, field, optional)
895 local o = self:option(Value, field, field)
896 o.optional = optional
897 end
898
899 -- Parse all dynamic options
900 function AbstractSection.parse_dynamic(self, section)
901 if not self.dynamic then
902 return
903 end
904
905 local arr = luci.util.clone(self:cfgvalue(section))
906 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
907 for k, v in pairs(form) do
908 arr[k] = v
909 end
910
911 for key,val in pairs(arr) do
912 local create = true
913
914 for i,c in ipairs(self.children) do
915 if c.option == key then
916 create = false
917 end
918 end
919
920 if create and key:sub(1, 1) ~= "." then
921 self.map.proceed = true
922 self:add_dynamic(key, true)
923 end
924 end
925 end
926
927 -- Returns the section's UCI table
928 function AbstractSection.cfgvalue(self, section)
929 return self.map:get(section)
930 end
931
932 -- Push events
933 function AbstractSection.push_events(self)
934 --luci.util.append(self.map.events, self.events)
935 self.map.changed = true
936 end
937
938 -- Removes the section
939 function AbstractSection.remove(self, section)
940 self.map.proceed = true
941 return self.map:del(section)
942 end
943
944 -- Creates the section
945 function AbstractSection.create(self, section)
946 local stat
947
948 if section then
949 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
950 else
951 section = self.map:add(self.sectiontype)
952 stat = section
953 end
954
955 if stat then
956 for k,v in pairs(self.children) do
957 if v.default then
958 self.map:set(section, v.option, v.default)
959 end
960 end
961
962 for k,v in pairs(self.defaults) do
963 self.map:set(section, k, v)
964 end
965 end
966
967 self.map.proceed = true
968
969 return stat
970 end
971
972
973 SimpleSection = class(AbstractSection)
974
975 function SimpleSection.__init__(self, form, ...)
976 AbstractSection.__init__(self, form, nil, ...)
977 self.template = "cbi/nullsection"
978 end
979
980
981 Table = class(AbstractSection)
982
983 function Table.__init__(self, form, data, ...)
984 local datasource = {}
985 local tself = self
986 datasource.config = "table"
987 self.data = data or {}
988
989 datasource.formvalue = Map.formvalue
990 datasource.formvaluetable = Map.formvaluetable
991 datasource.readinput = true
992
993 function datasource.get(self, section, option)
994 return tself.data[section] and tself.data[section][option]
995 end
996
997 function datasource.submitstate(self)
998 return Map.formvalue(self, "cbi.submit")
999 end
1000
1001 function datasource.del(...)
1002 return true
1003 end
1004
1005 function datasource.get_scheme()
1006 return nil
1007 end
1008
1009 AbstractSection.__init__(self, datasource, "table", ...)
1010 self.template = "cbi/tblsection"
1011 self.rowcolors = true
1012 self.anonymous = true
1013 end
1014
1015 function Table.parse(self, readinput)
1016 self.map.readinput = (readinput ~= false)
1017 for i, k in ipairs(self:cfgsections()) do
1018 if self.map:submitstate() then
1019 Node.parse(self, k)
1020 end
1021 end
1022 end
1023
1024 function Table.cfgsections(self)
1025 local sections = {}
1026
1027 for i, v in luci.util.kspairs(self.data) do
1028 table.insert(sections, i)
1029 end
1030
1031 return sections
1032 end
1033
1034 function Table.update(self, data)
1035 self.data = data
1036 end
1037
1038
1039
1040 --[[
1041 NamedSection - A fixed configuration section defined by its name
1042 ]]--
1043 NamedSection = class(AbstractSection)
1044
1045 function NamedSection.__init__(self, map, section, stype, ...)
1046 AbstractSection.__init__(self, map, stype, ...)
1047 Node._i18n(self, map.config, section, nil, ...)
1048
1049 -- Defaults
1050 self.addremove = false
1051
1052 -- Use defaults from UVL
1053 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1054 local vs = self.map:get_scheme(self.sectiontype)
1055 self.addremove = not vs.unique and not vs.required
1056 self.dynamic = vs.dynamic
1057 self.title = self.title or vs.title
1058 self.description = self.description or vs.descr
1059 end
1060
1061 self.template = "cbi/nsection"
1062 self.section = section
1063 end
1064
1065 function NamedSection.parse(self, novld)
1066 local s = self.section
1067 local active = self:cfgvalue(s)
1068
1069 if self.addremove then
1070 local path = self.config.."."..s
1071 if active then -- Remove the section
1072 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1073 self:push_events()
1074 return
1075 end
1076 else -- Create and apply default values
1077 if self.map:formvalue("cbi.cns."..path) then
1078 self:create(s)
1079 return
1080 end
1081 end
1082 end
1083
1084 if active then
1085 AbstractSection.parse_dynamic(self, s)
1086 if self.map:submitstate() then
1087 Node.parse(self, s)
1088
1089 if not novld and not self.override_scheme and self.map.scheme then
1090 _uvl_validate_section(self, s)
1091 end
1092 end
1093 AbstractSection.parse_optionals(self, s)
1094
1095 if self.changed then
1096 self:push_events()
1097 end
1098 end
1099 end
1100
1101
1102 --[[
1103 TypedSection - A (set of) configuration section(s) defined by the type
1104 addremove: Defines whether the user can add/remove sections of this type
1105 anonymous: Allow creating anonymous sections
1106 validate: a validation function returning nil if the section is invalid
1107 ]]--
1108 TypedSection = class(AbstractSection)
1109
1110 function TypedSection.__init__(self, map, type, ...)
1111 AbstractSection.__init__(self, map, type, ...)
1112 Node._i18n(self, map.config, type, nil, ...)
1113
1114 self.template = "cbi/tsection"
1115 self.deps = {}
1116 self.anonymous = false
1117
1118 -- Use defaults from UVL
1119 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1120 local vs = self.map:get_scheme(self.sectiontype)
1121 self.addremove = not vs.unique and not vs.required
1122 self.dynamic = vs.dynamic
1123 self.anonymous = not vs.named
1124 self.title = self.title or vs.title
1125 self.description = self.description or vs.descr
1126 end
1127 end
1128
1129 -- Return all matching UCI sections for this TypedSection
1130 function TypedSection.cfgsections(self)
1131 local sections = {}
1132 self.map.uci:foreach(self.map.config, self.sectiontype,
1133 function (section)
1134 if self:checkscope(section[".name"]) then
1135 table.insert(sections, section[".name"])
1136 end
1137 end)
1138
1139 return sections
1140 end
1141
1142 -- Limits scope to sections that have certain option => value pairs
1143 function TypedSection.depends(self, option, value)
1144 table.insert(self.deps, {option=option, value=value})
1145 end
1146
1147 function TypedSection.parse(self, novld)
1148 if self.addremove then
1149 -- Remove
1150 local crval = REMOVE_PREFIX .. self.config
1151 local name = self.map:formvaluetable(crval)
1152 for k,v in pairs(name) do
1153 if k:sub(-2) == ".x" then
1154 k = k:sub(1, #k - 2)
1155 end
1156 if self:cfgvalue(k) and self:checkscope(k) then
1157 self:remove(k)
1158 end
1159 end
1160 end
1161
1162 local co
1163 for i, k in ipairs(self:cfgsections()) do
1164 AbstractSection.parse_dynamic(self, k)
1165 if self.map:submitstate() then
1166 Node.parse(self, k, novld)
1167
1168 if not novld and not self.override_scheme and self.map.scheme then
1169 _uvl_validate_section(self, k)
1170 end
1171 end
1172 AbstractSection.parse_optionals(self, k)
1173 end
1174
1175 if self.addremove then
1176 -- Create
1177 local created
1178 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1179 local name = self.map:formvalue(crval)
1180 if self.anonymous then
1181 if name then
1182 created = self:create()
1183 end
1184 else
1185 if name then
1186 -- Ignore if it already exists
1187 if self:cfgvalue(name) then
1188 name = nil;
1189 end
1190
1191 name = self:checkscope(name)
1192
1193 if not name then
1194 self.err_invalid = true
1195 end
1196
1197 if name and #name > 0 then
1198 created = self:create(name) and name
1199 if not created then
1200 self.invalid_cts = true
1201 end
1202 end
1203 end
1204 end
1205
1206 if created then
1207 AbstractSection.parse_optionals(self, created)
1208 end
1209 end
1210
1211 if created or self.changed then
1212 self:push_events()
1213 end
1214 end
1215
1216 -- Verifies scope of sections
1217 function TypedSection.checkscope(self, section)
1218 -- Check if we are not excluded
1219 if self.filter and not self:filter(section) then
1220 return nil
1221 end
1222
1223 -- Check if at least one dependency is met
1224 if #self.deps > 0 and self:cfgvalue(section) then
1225 local stat = false
1226
1227 for k, v in ipairs(self.deps) do
1228 if self:cfgvalue(section)[v.option] == v.value then
1229 stat = true
1230 end
1231 end
1232
1233 if not stat then
1234 return nil
1235 end
1236 end
1237
1238 return self:validate(section)
1239 end
1240
1241
1242 -- Dummy validate function
1243 function TypedSection.validate(self, section)
1244 return section
1245 end
1246
1247
1248 --[[
1249 AbstractValue - An abstract Value Type
1250 null: Value can be empty
1251 valid: A function returning the value if it is valid otherwise nil
1252 depends: A table of option => value pairs of which one must be true
1253 default: The default value
1254 size: The size of the input fields
1255 rmempty: Unset value if empty
1256 optional: This value is optional (see AbstractSection.optionals)
1257 ]]--
1258 AbstractValue = class(Node)
1259
1260 function AbstractValue.__init__(self, map, section, option, ...)
1261 Node.__init__(self, ...)
1262 self.section = section
1263 self.option = option
1264 self.map = map
1265 self.config = map.config
1266 self.tag_invalid = {}
1267 self.tag_missing = {}
1268 self.tag_reqerror = {}
1269 self.tag_error = {}
1270 self.deps = {}
1271 --self.cast = "string"
1272
1273 self.track_missing = false
1274 self.rmempty = true
1275 self.default = nil
1276 self.size = nil
1277 self.optional = false
1278 end
1279
1280 function AbstractValue.prepare(self)
1281 -- Use defaults from UVL
1282 if not self.override_scheme
1283 and self.map:get_scheme(self.section.sectiontype, self.option) then
1284 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1285 if self.cast == nil then
1286 self.cast = (vs.type == "list") and "list" or "string"
1287 end
1288 self.title = self.title or vs.title
1289 self.description = self.description or vs.descr
1290 if self.default == nil then
1291 self.default = vs.default
1292 end
1293
1294 if vs.depends and not self.override_dependencies then
1295 for i, deps in ipairs(vs.depends) do
1296 deps = _uvl_strip_remote_dependencies(deps)
1297 if next(deps) then
1298 self:depends(deps)
1299 end
1300 end
1301 end
1302 end
1303
1304 self.cast = self.cast or "string"
1305 end
1306
1307 -- Add a dependencie to another section field
1308 function AbstractValue.depends(self, field, value)
1309 local deps
1310 if type(field) == "string" then
1311 deps = {}
1312 deps[field] = value
1313 else
1314 deps = field
1315 end
1316
1317 table.insert(self.deps, {deps=deps, add=""})
1318 end
1319
1320 -- Generates the unique CBID
1321 function AbstractValue.cbid(self, section)
1322 return "cbid."..self.map.config.."."..section.."."..self.option
1323 end
1324
1325 -- Return whether this object should be created
1326 function AbstractValue.formcreated(self, section)
1327 local key = "cbi.opt."..self.config.."."..section
1328 return (self.map:formvalue(key) == self.option)
1329 end
1330
1331 -- Returns the formvalue for this object
1332 function AbstractValue.formvalue(self, section)
1333 return self.map:formvalue(self:cbid(section))
1334 end
1335
1336 function AbstractValue.additional(self, value)
1337 self.optional = value
1338 end
1339
1340 function AbstractValue.mandatory(self, value)
1341 self.rmempty = not value
1342 end
1343
1344 function AbstractValue.parse(self, section, novld)
1345 local fvalue = self:formvalue(section)
1346 local cvalue = self:cfgvalue(section)
1347
1348 -- If favlue and cvalue are both tables and have the same content
1349 -- make them identical
1350 if type(fvalue) == "table" and type(cvalue) == "table" then
1351 local equal = #fvalue == #cvalue
1352 if equal then
1353 for i=1, #fvalue do
1354 if cvalue[i] ~= fvalue[i] then
1355 equal = false
1356 end
1357 end
1358 end
1359 if equal then
1360 fvalue = cvalue
1361 end
1362 end
1363
1364 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1365 fvalue = self:transform(self:validate(fvalue, section))
1366 if not fvalue and not novld then
1367 if self.error then
1368 self.error[section] = "invalid"
1369 else
1370 self.error = { [section] = "invalid" }
1371 end
1372 if self.section.error then
1373 table.insert(self.section.error[section], "invalid")
1374 else
1375 self.section.error = {[section] = {"invalid"}}
1376 end
1377 self.map.save = false
1378 end
1379 if fvalue and not (fvalue == cvalue) then
1380 if self:write(section, fvalue) then
1381 -- Push events
1382 self.section.changed = true
1383 --luci.util.append(self.map.events, self.events)
1384 end
1385 end
1386 else -- Unset the UCI or error
1387 if self.rmempty or self.optional then
1388 if self:remove(section) then
1389 -- Push events
1390 self.section.changed = true
1391 --luci.util.append(self.map.events, self.events)
1392 end
1393 elseif cvalue ~= fvalue and not novld then
1394 self:write(section, fvalue or "")
1395 if self.error then
1396 self.error[section] = "missing"
1397 else
1398 self.error = { [section] = "missing" }
1399 end
1400 self.map.save = false
1401 end
1402 end
1403 end
1404
1405 -- Render if this value exists or if it is mandatory
1406 function AbstractValue.render(self, s, scope)
1407 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1408 scope = scope or {}
1409 scope.section = s
1410 scope.cbid = self:cbid(s)
1411 scope.striptags = luci.util.striptags
1412 scope.pcdata = luci.util.pcdata
1413
1414 scope.ifattr = function(cond,key,val)
1415 if cond then
1416 return string.format(
1417 ' %s="%s"', tostring(key),
1418 luci.util.pcdata(tostring( val
1419 or scope[key]
1420 or (type(self[key]) ~= "function" and self[key])
1421 or "" ))
1422 )
1423 else
1424 return ''
1425 end
1426 end
1427
1428 scope.attr = function(...)
1429 return scope.ifattr( true, ... )
1430 end
1431
1432 Node.render(self, scope)
1433 end
1434 end
1435
1436 -- Return the UCI value of this object
1437 function AbstractValue.cfgvalue(self, section)
1438 local value = self.map:get(section, self.option)
1439 if not value then
1440 return nil
1441 elseif not self.cast or self.cast == type(value) then
1442 return value
1443 elseif self.cast == "string" then
1444 if type(value) == "table" then
1445 return value[1]
1446 end
1447 elseif self.cast == "table" then
1448 return luci.util.split(value, "%s+", nil, true)
1449 end
1450 end
1451
1452 -- Validate the form value
1453 function AbstractValue.validate(self, value)
1454 return value
1455 end
1456
1457 AbstractValue.transform = AbstractValue.validate
1458
1459
1460 -- Write to UCI
1461 function AbstractValue.write(self, section, value)
1462 return self.map:set(section, self.option, value)
1463 end
1464
1465 -- Remove from UCI
1466 function AbstractValue.remove(self, section)
1467 return self.map:del(section, self.option)
1468 end
1469
1470
1471
1472
1473 --[[
1474 Value - A one-line value
1475 maxlength: The maximum length
1476 ]]--
1477 Value = class(AbstractValue)
1478
1479 function Value.__init__(self, ...)
1480 AbstractValue.__init__(self, ...)
1481 self.template = "cbi/value"
1482 self.keylist = {}
1483 self.vallist = {}
1484 end
1485
1486 function Value.value(self, key, val)
1487 val = val or key
1488 table.insert(self.keylist, tostring(key))
1489 table.insert(self.vallist, tostring(val))
1490 end
1491
1492
1493 -- DummyValue - This does nothing except being there
1494 DummyValue = class(AbstractValue)
1495
1496 function DummyValue.__init__(self, ...)
1497 AbstractValue.__init__(self, ...)
1498 self.template = "cbi/dvalue"
1499 self.value = nil
1500 end
1501
1502 function DummyValue.cfgvalue(self, section)
1503 local value
1504 if self.value then
1505 if type(self.value) == "function" then
1506 value = self:value(section)
1507 else
1508 value = self.value
1509 end
1510 else
1511 value = AbstractValue.cfgvalue(self, section)
1512 end
1513 return value
1514 end
1515
1516 function DummyValue.parse(self)
1517
1518 end
1519
1520
1521 --[[
1522 Flag - A flag being enabled or disabled
1523 ]]--
1524 Flag = class(AbstractValue)
1525
1526 function Flag.__init__(self, ...)
1527 AbstractValue.__init__(self, ...)
1528 self.template = "cbi/fvalue"
1529
1530 self.enabled = "1"
1531 self.disabled = "0"
1532 end
1533
1534 -- A flag can only have two states: set or unset
1535 function Flag.parse(self, section)
1536 local fvalue = self:formvalue(section)
1537
1538 if fvalue then
1539 fvalue = self.enabled
1540 else
1541 fvalue = self.disabled
1542 end
1543
1544 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1545 if not(fvalue == self:cfgvalue(section)) then
1546 self:write(section, fvalue)
1547 end
1548 else
1549 self:remove(section)
1550 end
1551 end
1552
1553
1554
1555 --[[
1556 ListValue - A one-line value predefined in a list
1557 widget: The widget that will be used (select, radio)
1558 ]]--
1559 ListValue = class(AbstractValue)
1560
1561 function ListValue.__init__(self, ...)
1562 AbstractValue.__init__(self, ...)
1563 self.template = "cbi/lvalue"
1564
1565 self.keylist = {}
1566 self.vallist = {}
1567 self.size = 1
1568 self.widget = "select"
1569 end
1570
1571 function ListValue.prepare(self, ...)
1572 AbstractValue.prepare(self, ...)
1573 if not self.override_scheme
1574 and self.map:get_scheme(self.section.sectiontype, self.option) then
1575 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1576 if self.value and vs.valuelist and not self.override_values then
1577 for k, v in ipairs(vs.valuelist) do
1578 local deps = {}
1579 if not self.override_dependencies
1580 and vs.enum_depends and vs.enum_depends[v.value] then
1581 for i, dep in ipairs(vs.enum_depends[v.value]) do
1582 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1583 end
1584 end
1585 self:value(v.value, v.title or v.value, unpack(deps))
1586 end
1587 end
1588 end
1589 end
1590
1591 function ListValue.value(self, key, val, ...)
1592 if luci.util.contains(self.keylist, key) then
1593 return
1594 end
1595
1596 val = val or key
1597 table.insert(self.keylist, tostring(key))
1598 table.insert(self.vallist, tostring(val))
1599
1600 for i, deps in ipairs({...}) do
1601 table.insert(self.deps, {add = "-"..key, deps=deps})
1602 end
1603 end
1604
1605 function ListValue.validate(self, val)
1606 if luci.util.contains(self.keylist, val) then
1607 return val
1608 else
1609 return nil
1610 end
1611 end
1612
1613
1614
1615 --[[
1616 MultiValue - Multiple delimited values
1617 widget: The widget that will be used (select, checkbox)
1618 delimiter: The delimiter that will separate the values (default: " ")
1619 ]]--
1620 MultiValue = class(AbstractValue)
1621
1622 function MultiValue.__init__(self, ...)
1623 AbstractValue.__init__(self, ...)
1624 self.template = "cbi/mvalue"
1625
1626 self.keylist = {}
1627 self.vallist = {}
1628
1629 self.widget = "checkbox"
1630 self.delimiter = " "
1631 end
1632
1633 function MultiValue.render(self, ...)
1634 if self.widget == "select" and not self.size then
1635 self.size = #self.vallist
1636 end
1637
1638 AbstractValue.render(self, ...)
1639 end
1640
1641 function MultiValue.value(self, key, val)
1642 if luci.util.contains(self.keylist, key) then
1643 return
1644 end
1645
1646 val = val or key
1647 table.insert(self.keylist, tostring(key))
1648 table.insert(self.vallist, tostring(val))
1649 end
1650
1651 function MultiValue.valuelist(self, section)
1652 local val = self:cfgvalue(section)
1653
1654 if not(type(val) == "string") then
1655 return {}
1656 end
1657
1658 return luci.util.split(val, self.delimiter)
1659 end
1660
1661 function MultiValue.validate(self, val)
1662 val = (type(val) == "table") and val or {val}
1663
1664 local result
1665
1666 for i, value in ipairs(val) do
1667 if luci.util.contains(self.keylist, value) then
1668 result = result and (result .. self.delimiter .. value) or value
1669 end
1670 end
1671
1672 return result
1673 end
1674
1675
1676 StaticList = class(MultiValue)
1677
1678 function StaticList.__init__(self, ...)
1679 MultiValue.__init__(self, ...)
1680 self.cast = "table"
1681 self.valuelist = self.cfgvalue
1682
1683 if not self.override_scheme
1684 and self.map:get_scheme(self.section.sectiontype, self.option) then
1685 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1686 if self.value and vs.values and not self.override_values then
1687 for k, v in pairs(vs.values) do
1688 self:value(k, v)
1689 end
1690 end
1691 end
1692 end
1693
1694 function StaticList.validate(self, value)
1695 value = (type(value) == "table") and value or {value}
1696
1697 local valid = {}
1698 for i, v in ipairs(value) do
1699 if luci.util.contains(self.keylist, v) then
1700 table.insert(valid, v)
1701 end
1702 end
1703 return valid
1704 end
1705
1706
1707 DynamicList = class(AbstractValue)
1708
1709 function DynamicList.__init__(self, ...)
1710 AbstractValue.__init__(self, ...)
1711 self.template = "cbi/dynlist"
1712 self.cast = "table"
1713 self.keylist = {}
1714 self.vallist = {}
1715 end
1716
1717 function DynamicList.value(self, key, val)
1718 val = val or key
1719 table.insert(self.keylist, tostring(key))
1720 table.insert(self.vallist, tostring(val))
1721 end
1722
1723 function DynamicList.write(self, ...)
1724 self.map.proceed = true
1725 return AbstractValue.write(self, ...)
1726 end
1727
1728 function DynamicList.formvalue(self, section)
1729 local value = AbstractValue.formvalue(self, section)
1730 value = (type(value) == "table") and value or {value}
1731
1732 local valid = {}
1733 for i, v in ipairs(value) do
1734 if v and #v > 0
1735 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1736 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1737 table.insert(valid, v)
1738 end
1739 end
1740
1741 return valid
1742 end
1743
1744
1745 --[[
1746 TextValue - A multi-line value
1747 rows: Rows
1748 ]]--
1749 TextValue = class(AbstractValue)
1750
1751 function TextValue.__init__(self, ...)
1752 AbstractValue.__init__(self, ...)
1753 self.template = "cbi/tvalue"
1754 end
1755
1756 --[[
1757 Button
1758 ]]--
1759 Button = class(AbstractValue)
1760
1761 function Button.__init__(self, ...)
1762 AbstractValue.__init__(self, ...)
1763 self.template = "cbi/button"
1764 self.inputstyle = nil
1765 self.rmempty = true
1766 end
1767
1768
1769 FileUpload = class(AbstractValue)
1770
1771 function FileUpload.__init__(self, ...)
1772 AbstractValue.__init__(self, ...)
1773 self.template = "cbi/upload"
1774 if not self.map.upload_fields then
1775 self.map.upload_fields = { self }
1776 else
1777 self.map.upload_fields[#self.map.upload_fields+1] = self
1778 end
1779 end
1780
1781 function FileUpload.formcreated(self, section)
1782 return AbstractValue.formcreated(self, section) or
1783 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1784 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1785 end
1786
1787 function FileUpload.cfgvalue(self, section)
1788 local val = AbstractValue.cfgvalue(self, section)
1789 if val and fs.access(val) then
1790 return val
1791 end
1792 return nil
1793 end
1794
1795 function FileUpload.formvalue(self, section)
1796 local val = AbstractValue.formvalue(self, section)
1797 if val then
1798 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1799 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1800 then
1801 return val
1802 end
1803 fs.unlink(val)
1804 self.value = nil
1805 end
1806 return nil
1807 end
1808
1809 function FileUpload.remove(self, section)
1810 local val = AbstractValue.formvalue(self, section)
1811 if val and fs.access(val) then fs.unlink(val) end
1812 return AbstractValue.remove(self, section)
1813 end
1814
1815
1816 FileBrowser = class(AbstractValue)
1817
1818 function FileBrowser.__init__(self, ...)
1819 AbstractValue.__init__(self, ...)
1820 self.template = "cbi/browser"
1821 end