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