CBI: Fix subdependencies
[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 self:_run_hooks("on_parse")
355
356 if self:formvalue("cbi.skip") then
357 self.state = FORM_SKIP
358 return self:state_handler(self.state)
359 end
360
361 Node.parse(self, ...)
362
363 if self.save then
364 for i, config in ipairs(self.parsechain) do
365 self.uci:save(config)
366 end
367 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
368 self:_run_hooks("on_before_commit")
369 for i, config in ipairs(self.parsechain) do
370 self.uci:commit(config)
371
372 -- Refresh data because commit changes section names
373 self.uci:load(config)
374 end
375 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
376 if self.apply_on_parse then
377 self.uci:apply(self.parsechain)
378 self:_run_hooks("on_apply", "on_after_apply")
379 else
380 self._apply = function()
381 local cmd = self.uci:apply(self.parsechain, true)
382 return io.popen(cmd)
383 end
384 end
385
386 -- Reparse sections
387 Node.parse(self, true)
388
389 end
390 for i, config in ipairs(self.parsechain) do
391 self.uci:unload(config)
392 end
393 if type(self.commit_handler) == "function" then
394 self:commit_handler(self:submitstate())
395 end
396 end
397
398 if self:submitstate() then
399 if not self.save then
400 self.state = FORM_INVALID
401 elseif self.proceed then
402 self.state = FORM_PROCEED
403 else
404 self.state = self.changed and FORM_CHANGED or FORM_VALID
405 end
406 else
407 self.state = FORM_NODATA
408 end
409
410 return self:state_handler(self.state)
411 end
412
413 function Map.render(self, ...)
414 self:_run_hooks("on_init")
415 Node.render(self, ...)
416 if self._apply then
417 local fp = self._apply()
418 fp:read("*a")
419 fp:close()
420 self:_run_hooks("on_apply")
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:_run_hooks("on_cancel") then
565 return FORM_DONE
566 end
567 end
568
569 if not Map.formvalue(self, "cbi.delg.current") then
570 self:_run_hooks("on_init")
571 end
572
573 local newcurrent
574 self.chain = self.chain or self:get_chain()
575 self.current = self.current or self:get_active()
576 self.active = self.active or self:get(self.current)
577 assert(self.active, "Invalid state")
578
579 local stat = FORM_DONE
580 if type(self.active) ~= "function" then
581 self.active:populate_delegator(self)
582 stat = self.active:parse()
583 else
584 self:active()
585 end
586
587 if stat > FORM_PROCEED then
588 if Map.formvalue(self, "cbi.delg.back") then
589 newcurrent = self:get_prev(self.current)
590 else
591 newcurrent = self:get_next(self.current)
592 end
593 elseif stat < FORM_PROCEED then
594 return stat
595 end
596
597
598 if not Map.formvalue(self, "cbi.submit") then
599 return FORM_NODATA
600 elseif stat > FORM_PROCEED
601 and (not newcurrent or not self:get(newcurrent)) then
602 self:_run_hooks("on_done")
603 return FORM_DONE
604 else
605 self.current = newcurrent or self.current
606 self.active = self:get(self.current)
607 if type(self.active) ~= "function" then
608 self.active:parse(false)
609 return FROM_PROCEED
610 else
611 return self:parse(...)
612 end
613 end
614 end
615
616 function Delegator.get_next(self, state)
617 for k, v in ipairs(self.chain) do
618 if v == state then
619 return self.chain[k+1]
620 end
621 end
622 end
623
624 function Delegator.get_prev(self, state)
625 for k, v in ipairs(self.chain) do
626 if v == state then
627 return self.chain[k-1]
628 end
629 end
630 end
631
632 function Delegator.get_chain(self)
633 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
634 return type(x) == "table" and x or {x}
635 end
636
637 function Delegator.get_active(self)
638 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
639 end
640
641 --[[
642 Page - A simple node
643 ]]--
644
645 Page = class(Node)
646 Page.__init__ = Node.__init__
647 Page.parse = function() end
648
649
650 --[[
651 SimpleForm - A Simple non-UCI form
652 ]]--
653 SimpleForm = class(Node)
654
655 function SimpleForm.__init__(self, config, title, description, data)
656 Node.__init__(self, title, description)
657 self.config = config
658 self.data = data or {}
659 self.template = "cbi/simpleform"
660 self.dorender = true
661 self.pageaction = false
662 self.readinput = true
663 end
664
665 SimpleForm.formvalue = Map.formvalue
666 SimpleForm.formvaluetable = Map.formvaluetable
667
668 function SimpleForm.parse(self, readinput, ...)
669 self.readinput = (readinput ~= false)
670
671 if self:formvalue("cbi.skip") then
672 return FORM_SKIP
673 end
674
675 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
676 return FORM_DONE
677 end
678
679 if self:submitstate() then
680 Node.parse(self, 1, ...)
681 end
682
683 local valid = true
684 for k, j in ipairs(self.children) do
685 for i, v in ipairs(j.children) do
686 valid = valid
687 and (not v.tag_missing or not v.tag_missing[1])
688 and (not v.tag_invalid or not v.tag_invalid[1])
689 and (not v.error)
690 end
691 end
692
693 local state =
694 not self:submitstate() and FORM_NODATA
695 or valid and FORM_VALID
696 or FORM_INVALID
697
698 self.dorender = not self.handle
699 if self.handle then
700 local nrender, nstate = self:handle(state, self.data)
701 self.dorender = self.dorender or (nrender ~= false)
702 state = nstate or state
703 end
704 return state
705 end
706
707 function SimpleForm.render(self, ...)
708 if self.dorender then
709 Node.render(self, ...)
710 end
711 end
712
713 function SimpleForm.submitstate(self)
714 return self:formvalue("cbi.submit")
715 end
716
717 function SimpleForm.section(self, class, ...)
718 if instanceof(class, AbstractSection) then
719 local obj = class(self, ...)
720 self:append(obj)
721 return obj
722 else
723 error("class must be a descendent of AbstractSection")
724 end
725 end
726
727 -- Creates a child field
728 function SimpleForm.field(self, class, ...)
729 local section
730 for k, v in ipairs(self.children) do
731 if instanceof(v, SimpleSection) then
732 section = v
733 break
734 end
735 end
736 if not section then
737 section = self:section(SimpleSection)
738 end
739
740 if instanceof(class, AbstractValue) then
741 local obj = class(self, section, ...)
742 obj.track_missing = true
743 section:append(obj)
744 return obj
745 else
746 error("class must be a descendent of AbstractValue")
747 end
748 end
749
750 function SimpleForm.set(self, section, option, value)
751 self.data[option] = value
752 end
753
754
755 function SimpleForm.del(self, section, option)
756 self.data[option] = nil
757 end
758
759
760 function SimpleForm.get(self, section, option)
761 return self.data[option]
762 end
763
764
765 function SimpleForm.get_scheme()
766 return nil
767 end
768
769
770 Form = class(SimpleForm)
771
772 function Form.__init__(self, ...)
773 SimpleForm.__init__(self, ...)
774 self.embedded = true
775 end
776
777
778 --[[
779 AbstractSection
780 ]]--
781 AbstractSection = class(Node)
782
783 function AbstractSection.__init__(self, map, sectiontype, ...)
784 Node.__init__(self, ...)
785 self.sectiontype = sectiontype
786 self.map = map
787 self.config = map.config
788 self.optionals = {}
789 self.defaults = {}
790 self.fields = {}
791 self.tag_error = {}
792 self.tag_invalid = {}
793 self.tag_deperror = {}
794 self.changed = false
795
796 self.optional = true
797 self.addremove = false
798 self.dynamic = false
799 end
800
801 -- Define a tab for the section
802 function AbstractSection.tab(self, tab, title, desc)
803 self.tabs = self.tabs or { }
804 self.tab_names = self.tab_names or { }
805
806 self.tab_names[#self.tab_names+1] = tab
807 self.tabs[tab] = {
808 title = title,
809 description = desc,
810 childs = { }
811 }
812 end
813
814 -- Appends a new option
815 function AbstractSection.option(self, class, option, ...)
816 -- Autodetect from UVL
817 if class == true and self.map:get_scheme(self.sectiontype, option) then
818 local vs = self.map:get_scheme(self.sectiontype, option)
819 if vs.type == "boolean" then
820 class = Flag
821 elseif vs.type == "list" then
822 class = DynamicList
823 elseif vs.type == "enum" or vs.type == "reference" then
824 class = ListValue
825 else
826 class = Value
827 end
828 end
829
830 if instanceof(class, AbstractValue) then
831 local obj = class(self.map, self, option, ...)
832 self:append(obj)
833 self.fields[option] = obj
834 return obj
835 elseif class == true then
836 error("No valid class was given and autodetection failed.")
837 else
838 error("class must be a descendant of AbstractValue")
839 end
840 end
841
842 -- Appends a new tabbed option
843 function AbstractSection.taboption(self, tab, ...)
844
845 assert(tab and self.tabs and self.tabs[tab],
846 "Cannot assign option to not existing tab %q" % tostring(tab))
847
848 local l = self.tabs[tab].childs
849 local o = AbstractSection.option(self, ...)
850
851 if o then l[#l+1] = o end
852
853 return o
854 end
855
856 -- Render a single tab
857 function AbstractSection.render_tab(self, tab, ...)
858
859 assert(tab and self.tabs and self.tabs[tab],
860 "Cannot render not existing tab %q" % tostring(tab))
861
862 for _, node in ipairs(self.tabs[tab].childs) do
863 node:render(...)
864 end
865 end
866
867 -- Parse optional options
868 function AbstractSection.parse_optionals(self, section)
869 if not self.optional then
870 return
871 end
872
873 self.optionals[section] = {}
874
875 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
876 for k,v in ipairs(self.children) do
877 if v.optional and not v:cfgvalue(section) then
878 if field == v.option then
879 field = nil
880 self.map.proceed = true
881 else
882 table.insert(self.optionals[section], v)
883 end
884 end
885 end
886
887 if field and #field > 0 and self.dynamic then
888 self:add_dynamic(field)
889 end
890 end
891
892 -- Add a dynamic option
893 function AbstractSection.add_dynamic(self, field, optional)
894 local o = self:option(Value, field, field)
895 o.optional = optional
896 end
897
898 -- Parse all dynamic options
899 function AbstractSection.parse_dynamic(self, section)
900 if not self.dynamic then
901 return
902 end
903
904 local arr = luci.util.clone(self:cfgvalue(section))
905 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
906 for k, v in pairs(form) do
907 arr[k] = v
908 end
909
910 for key,val in pairs(arr) do
911 local create = true
912
913 for i,c in ipairs(self.children) do
914 if c.option == key then
915 create = false
916 end
917 end
918
919 if create and key:sub(1, 1) ~= "." then
920 self.map.proceed = true
921 self:add_dynamic(key, true)
922 end
923 end
924 end
925
926 -- Returns the section's UCI table
927 function AbstractSection.cfgvalue(self, section)
928 return self.map:get(section)
929 end
930
931 -- Push events
932 function AbstractSection.push_events(self)
933 --luci.util.append(self.map.events, self.events)
934 self.map.changed = true
935 end
936
937 -- Removes the section
938 function AbstractSection.remove(self, section)
939 self.map.proceed = true
940 return self.map:del(section)
941 end
942
943 -- Creates the section
944 function AbstractSection.create(self, section)
945 local stat
946
947 if section then
948 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
949 else
950 section = self.map:add(self.sectiontype)
951 stat = section
952 end
953
954 if stat then
955 for k,v in pairs(self.children) do
956 if v.default then
957 self.map:set(section, v.option, v.default)
958 end
959 end
960
961 for k,v in pairs(self.defaults) do
962 self.map:set(section, k, v)
963 end
964 end
965
966 self.map.proceed = true
967
968 return stat
969 end
970
971
972 SimpleSection = class(AbstractSection)
973
974 function SimpleSection.__init__(self, form, ...)
975 AbstractSection.__init__(self, form, nil, ...)
976 self.template = "cbi/nullsection"
977 end
978
979
980 Table = class(AbstractSection)
981
982 function Table.__init__(self, form, data, ...)
983 local datasource = {}
984 local tself = self
985 datasource.config = "table"
986 self.data = data or {}
987
988 datasource.formvalue = Map.formvalue
989 datasource.formvaluetable = Map.formvaluetable
990 datasource.readinput = true
991
992 function datasource.get(self, section, option)
993 return tself.data[section] and tself.data[section][option]
994 end
995
996 function datasource.submitstate(self)
997 return Map.formvalue(self, "cbi.submit")
998 end
999
1000 function datasource.del(...)
1001 return true
1002 end
1003
1004 function datasource.get_scheme()
1005 return nil
1006 end
1007
1008 AbstractSection.__init__(self, datasource, "table", ...)
1009 self.template = "cbi/tblsection"
1010 self.rowcolors = true
1011 self.anonymous = true
1012 end
1013
1014 function Table.parse(self, readinput)
1015 self.map.readinput = (readinput ~= false)
1016 for i, k in ipairs(self:cfgsections()) do
1017 if self.map:submitstate() then
1018 Node.parse(self, k)
1019 end
1020 end
1021 end
1022
1023 function Table.cfgsections(self)
1024 local sections = {}
1025
1026 for i, v in luci.util.kspairs(self.data) do
1027 table.insert(sections, i)
1028 end
1029
1030 return sections
1031 end
1032
1033 function Table.update(self, data)
1034 self.data = data
1035 end
1036
1037
1038
1039 --[[
1040 NamedSection - A fixed configuration section defined by its name
1041 ]]--
1042 NamedSection = class(AbstractSection)
1043
1044 function NamedSection.__init__(self, map, section, stype, ...)
1045 AbstractSection.__init__(self, map, stype, ...)
1046
1047 -- Defaults
1048 self.addremove = false
1049
1050 -- Use defaults from UVL
1051 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1052 local vs = self.map:get_scheme(self.sectiontype)
1053 self.addremove = not vs.unique and not vs.required
1054 self.dynamic = vs.dynamic
1055 self.title = self.title or vs.title
1056 self.description = self.description or vs.descr
1057 end
1058
1059 self.template = "cbi/nsection"
1060 self.section = section
1061 end
1062
1063 function NamedSection.parse(self, novld)
1064 local s = self.section
1065 local active = self:cfgvalue(s)
1066
1067 if self.addremove then
1068 local path = self.config.."."..s
1069 if active then -- Remove the section
1070 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1071 self:push_events()
1072 return
1073 end
1074 else -- Create and apply default values
1075 if self.map:formvalue("cbi.cns."..path) then
1076 self:create(s)
1077 return
1078 end
1079 end
1080 end
1081
1082 if active then
1083 AbstractSection.parse_dynamic(self, s)
1084 if self.map:submitstate() then
1085 Node.parse(self, s)
1086
1087 if not novld and not self.override_scheme and self.map.scheme then
1088 _uvl_validate_section(self, s)
1089 end
1090 end
1091 AbstractSection.parse_optionals(self, s)
1092
1093 if self.changed then
1094 self:push_events()
1095 end
1096 end
1097 end
1098
1099
1100 --[[
1101 TypedSection - A (set of) configuration section(s) defined by the type
1102 addremove: Defines whether the user can add/remove sections of this type
1103 anonymous: Allow creating anonymous sections
1104 validate: a validation function returning nil if the section is invalid
1105 ]]--
1106 TypedSection = class(AbstractSection)
1107
1108 function TypedSection.__init__(self, map, type, ...)
1109 AbstractSection.__init__(self, map, type, ...)
1110
1111 self.template = "cbi/tsection"
1112 self.deps = {}
1113 self.anonymous = false
1114
1115 -- Use defaults from UVL
1116 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1117 local vs = self.map:get_scheme(self.sectiontype)
1118 self.addremove = not vs.unique and not vs.required
1119 self.dynamic = vs.dynamic
1120 self.anonymous = not vs.named
1121 self.title = self.title or vs.title
1122 self.description = self.description or vs.descr
1123 end
1124 end
1125
1126 -- Return all matching UCI sections for this TypedSection
1127 function TypedSection.cfgsections(self)
1128 local sections = {}
1129 self.map.uci:foreach(self.map.config, self.sectiontype,
1130 function (section)
1131 if self:checkscope(section[".name"]) then
1132 table.insert(sections, section[".name"])
1133 end
1134 end)
1135
1136 return sections
1137 end
1138
1139 -- Limits scope to sections that have certain option => value pairs
1140 function TypedSection.depends(self, option, value)
1141 table.insert(self.deps, {option=option, value=value})
1142 end
1143
1144 function TypedSection.parse(self, novld)
1145 if self.addremove then
1146 -- Remove
1147 local crval = REMOVE_PREFIX .. self.config
1148 local name = self.map:formvaluetable(crval)
1149 for k,v in pairs(name) do
1150 if k:sub(-2) == ".x" then
1151 k = k:sub(1, #k - 2)
1152 end
1153 if self:cfgvalue(k) and self:checkscope(k) then
1154 self:remove(k)
1155 end
1156 end
1157 end
1158
1159 local co
1160 for i, k in ipairs(self:cfgsections()) do
1161 AbstractSection.parse_dynamic(self, k)
1162 if self.map:submitstate() then
1163 Node.parse(self, k, novld)
1164
1165 if not novld and not self.override_scheme and self.map.scheme then
1166 _uvl_validate_section(self, k)
1167 end
1168 end
1169 AbstractSection.parse_optionals(self, k)
1170 end
1171
1172 if self.addremove then
1173 -- Create
1174 local created
1175 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1176 local name = self.map:formvalue(crval)
1177 if self.anonymous then
1178 if name then
1179 created = self:create()
1180 end
1181 else
1182 if name then
1183 -- Ignore if it already exists
1184 if self:cfgvalue(name) then
1185 name = nil;
1186 end
1187
1188 name = self:checkscope(name)
1189
1190 if not name then
1191 self.err_invalid = true
1192 end
1193
1194 if name and #name > 0 then
1195 created = self:create(name) and name
1196 if not created then
1197 self.invalid_cts = true
1198 end
1199 end
1200 end
1201 end
1202
1203 if created then
1204 AbstractSection.parse_optionals(self, created)
1205 end
1206 end
1207
1208 if created or self.changed then
1209 self:push_events()
1210 end
1211 end
1212
1213 -- Verifies scope of sections
1214 function TypedSection.checkscope(self, section)
1215 -- Check if we are not excluded
1216 if self.filter and not self:filter(section) then
1217 return nil
1218 end
1219
1220 -- Check if at least one dependency is met
1221 if #self.deps > 0 and self:cfgvalue(section) then
1222 local stat = false
1223
1224 for k, v in ipairs(self.deps) do
1225 if self:cfgvalue(section)[v.option] == v.value then
1226 stat = true
1227 end
1228 end
1229
1230 if not stat then
1231 return nil
1232 end
1233 end
1234
1235 return self:validate(section)
1236 end
1237
1238
1239 -- Dummy validate function
1240 function TypedSection.validate(self, section)
1241 return section
1242 end
1243
1244
1245 --[[
1246 AbstractValue - An abstract Value Type
1247 null: Value can be empty
1248 valid: A function returning the value if it is valid otherwise nil
1249 depends: A table of option => value pairs of which one must be true
1250 default: The default value
1251 size: The size of the input fields
1252 rmempty: Unset value if empty
1253 optional: This value is optional (see AbstractSection.optionals)
1254 ]]--
1255 AbstractValue = class(Node)
1256
1257 function AbstractValue.__init__(self, map, section, option, ...)
1258 Node.__init__(self, ...)
1259 self.section = section
1260 self.option = option
1261 self.map = map
1262 self.config = map.config
1263 self.tag_invalid = {}
1264 self.tag_missing = {}
1265 self.tag_reqerror = {}
1266 self.tag_error = {}
1267 self.deps = {}
1268 self.subdeps = {}
1269 --self.cast = "string"
1270
1271 self.track_missing = false
1272 self.rmempty = true
1273 self.default = nil
1274 self.size = nil
1275 self.optional = false
1276 end
1277
1278 function AbstractValue.prepare(self)
1279 -- Use defaults from UVL
1280 if not self.override_scheme
1281 and self.map:get_scheme(self.section.sectiontype, self.option) then
1282 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1283 if self.cast == nil then
1284 self.cast = (vs.type == "list") and "list" or "string"
1285 end
1286 self.title = self.title or vs.title
1287 self.description = self.description or vs.descr
1288 if self.default == nil then
1289 self.default = vs.default
1290 end
1291
1292 if vs.depends and not self.override_dependencies then
1293 for i, deps in ipairs(vs.depends) do
1294 deps = _uvl_strip_remote_dependencies(deps)
1295 if next(deps) then
1296 self:depends(deps)
1297 end
1298 end
1299 end
1300 end
1301
1302 self.cast = self.cast or "string"
1303 end
1304
1305 -- Add a dependencie to another section field
1306 function AbstractValue.depends(self, field, value)
1307 local deps
1308 if type(field) == "string" then
1309 deps = {}
1310 deps[field] = value
1311 else
1312 deps = field
1313 end
1314
1315 table.insert(self.deps, {deps=deps, add=""})
1316 end
1317
1318 -- Generates the unique CBID
1319 function AbstractValue.cbid(self, section)
1320 return "cbid."..self.map.config.."."..section.."."..self.option
1321 end
1322
1323 -- Return whether this object should be created
1324 function AbstractValue.formcreated(self, section)
1325 local key = "cbi.opt."..self.config.."."..section
1326 return (self.map:formvalue(key) == self.option)
1327 end
1328
1329 -- Returns the formvalue for this object
1330 function AbstractValue.formvalue(self, section)
1331 return self.map:formvalue(self:cbid(section))
1332 end
1333
1334 function AbstractValue.additional(self, value)
1335 self.optional = value
1336 end
1337
1338 function AbstractValue.mandatory(self, value)
1339 self.rmempty = not value
1340 end
1341
1342 function AbstractValue.parse(self, section, novld)
1343 local fvalue = self:formvalue(section)
1344 local cvalue = self:cfgvalue(section)
1345
1346 -- If favlue and cvalue are both tables and have the same content
1347 -- make them identical
1348 if type(fvalue) == "table" and type(cvalue) == "table" then
1349 local equal = #fvalue == #cvalue
1350 if equal then
1351 for i=1, #fvalue do
1352 if cvalue[i] ~= fvalue[i] then
1353 equal = false
1354 end
1355 end
1356 end
1357 if equal then
1358 fvalue = cvalue
1359 end
1360 end
1361
1362 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1363 fvalue = self:transform(self:validate(fvalue, section))
1364 if not fvalue and not novld then
1365 if self.error then
1366 self.error[section] = "invalid"
1367 else
1368 self.error = { [section] = "invalid" }
1369 end
1370 if self.section.error then
1371 table.insert(self.section.error[section], "invalid")
1372 else
1373 self.section.error = {[section] = {"invalid"}}
1374 end
1375 self.map.save = false
1376 end
1377 if fvalue and not (fvalue == cvalue) then
1378 if self:write(section, fvalue) then
1379 -- Push events
1380 self.section.changed = true
1381 --luci.util.append(self.map.events, self.events)
1382 end
1383 end
1384 else -- Unset the UCI or error
1385 if self.rmempty or self.optional then
1386 if self:remove(section) then
1387 -- Push events
1388 self.section.changed = true
1389 --luci.util.append(self.map.events, self.events)
1390 end
1391 elseif cvalue ~= fvalue and not novld then
1392 self:write(section, fvalue or "")
1393 if self.error then
1394 self.error[section] = "missing"
1395 else
1396 self.error = { [section] = "missing" }
1397 end
1398 self.map.save = false
1399 end
1400 end
1401 end
1402
1403 -- Render if this value exists or if it is mandatory
1404 function AbstractValue.render(self, s, scope)
1405 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1406 scope = scope or {}
1407 scope.section = s
1408 scope.cbid = self:cbid(s)
1409 scope.striptags = luci.util.striptags
1410 scope.pcdata = luci.util.pcdata
1411
1412 scope.ifattr = function(cond,key,val)
1413 if cond then
1414 return string.format(
1415 ' %s="%s"', tostring(key),
1416 luci.util.pcdata(tostring( val
1417 or scope[key]
1418 or (type(self[key]) ~= "function" and self[key])
1419 or "" ))
1420 )
1421 else
1422 return ''
1423 end
1424 end
1425
1426 scope.attr = function(...)
1427 return scope.ifattr( true, ... )
1428 end
1429
1430 Node.render(self, scope)
1431 end
1432 end
1433
1434 -- Return the UCI value of this object
1435 function AbstractValue.cfgvalue(self, section)
1436 local value = self.map:get(section, self.option)
1437 if not value then
1438 return nil
1439 elseif not self.cast or self.cast == type(value) then
1440 return value
1441 elseif self.cast == "string" then
1442 if type(value) == "table" then
1443 return value[1]
1444 end
1445 elseif self.cast == "table" then
1446 return luci.util.split(value, "%s+", nil, true)
1447 end
1448 end
1449
1450 -- Validate the form value
1451 function AbstractValue.validate(self, value)
1452 return value
1453 end
1454
1455 AbstractValue.transform = AbstractValue.validate
1456
1457
1458 -- Write to UCI
1459 function AbstractValue.write(self, section, value)
1460 return self.map:set(section, self.option, value)
1461 end
1462
1463 -- Remove from UCI
1464 function AbstractValue.remove(self, section)
1465 return self.map:del(section, self.option)
1466 end
1467
1468
1469
1470
1471 --[[
1472 Value - A one-line value
1473 maxlength: The maximum length
1474 ]]--
1475 Value = class(AbstractValue)
1476
1477 function Value.__init__(self, ...)
1478 AbstractValue.__init__(self, ...)
1479 self.template = "cbi/value"
1480 self.keylist = {}
1481 self.vallist = {}
1482 end
1483
1484 function Value.value(self, key, val)
1485 val = val or key
1486 table.insert(self.keylist, tostring(key))
1487 table.insert(self.vallist, tostring(val))
1488 end
1489
1490
1491 -- DummyValue - This does nothing except being there
1492 DummyValue = class(AbstractValue)
1493
1494 function DummyValue.__init__(self, ...)
1495 AbstractValue.__init__(self, ...)
1496 self.template = "cbi/dvalue"
1497 self.value = nil
1498 end
1499
1500 function DummyValue.cfgvalue(self, section)
1501 local value
1502 if self.value then
1503 if type(self.value) == "function" then
1504 value = self:value(section)
1505 else
1506 value = self.value
1507 end
1508 else
1509 value = AbstractValue.cfgvalue(self, section)
1510 end
1511 return value
1512 end
1513
1514 function DummyValue.parse(self)
1515
1516 end
1517
1518
1519 --[[
1520 Flag - A flag being enabled or disabled
1521 ]]--
1522 Flag = class(AbstractValue)
1523
1524 function Flag.__init__(self, ...)
1525 AbstractValue.__init__(self, ...)
1526 self.template = "cbi/fvalue"
1527
1528 self.enabled = "1"
1529 self.disabled = "0"
1530 end
1531
1532 -- A flag can only have two states: set or unset
1533 function Flag.parse(self, section)
1534 local fvalue = self:formvalue(section)
1535
1536 if fvalue then
1537 fvalue = self.enabled
1538 else
1539 fvalue = self.disabled
1540 end
1541
1542 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1543 if not(fvalue == self:cfgvalue(section)) then
1544 self:write(section, fvalue)
1545 end
1546 else
1547 self:remove(section)
1548 end
1549 end
1550
1551
1552
1553 --[[
1554 ListValue - A one-line value predefined in a list
1555 widget: The widget that will be used (select, radio)
1556 ]]--
1557 ListValue = class(AbstractValue)
1558
1559 function ListValue.__init__(self, ...)
1560 AbstractValue.__init__(self, ...)
1561 self.template = "cbi/lvalue"
1562
1563 self.keylist = {}
1564 self.vallist = {}
1565 self.size = 1
1566 self.widget = "select"
1567 end
1568
1569 function ListValue.prepare(self, ...)
1570 AbstractValue.prepare(self, ...)
1571 if not self.override_scheme
1572 and self.map:get_scheme(self.section.sectiontype, self.option) then
1573 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1574 if self.value and vs.valuelist and not self.override_values then
1575 for k, v in ipairs(vs.valuelist) do
1576 local deps = {}
1577 if not self.override_dependencies
1578 and vs.enum_depends and vs.enum_depends[v.value] then
1579 for i, dep in ipairs(vs.enum_depends[v.value]) do
1580 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1581 end
1582 end
1583 self:value(v.value, v.title or v.value, unpack(deps))
1584 end
1585 end
1586 end
1587 end
1588
1589 function ListValue.value(self, key, val, ...)
1590 if luci.util.contains(self.keylist, key) then
1591 return
1592 end
1593
1594 val = val or key
1595 table.insert(self.keylist, tostring(key))
1596 table.insert(self.vallist, tostring(val))
1597
1598 for i, deps in ipairs({...}) do
1599 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1600 end
1601 end
1602
1603 function ListValue.validate(self, val)
1604 if luci.util.contains(self.keylist, val) then
1605 return val
1606 else
1607 return nil
1608 end
1609 end
1610
1611
1612
1613 --[[
1614 MultiValue - Multiple delimited values
1615 widget: The widget that will be used (select, checkbox)
1616 delimiter: The delimiter that will separate the values (default: " ")
1617 ]]--
1618 MultiValue = class(AbstractValue)
1619
1620 function MultiValue.__init__(self, ...)
1621 AbstractValue.__init__(self, ...)
1622 self.template = "cbi/mvalue"
1623
1624 self.keylist = {}
1625 self.vallist = {}
1626
1627 self.widget = "checkbox"
1628 self.delimiter = " "
1629 end
1630
1631 function MultiValue.render(self, ...)
1632 if self.widget == "select" and not self.size then
1633 self.size = #self.vallist
1634 end
1635
1636 AbstractValue.render(self, ...)
1637 end
1638
1639 function MultiValue.value(self, key, val)
1640 if luci.util.contains(self.keylist, key) then
1641 return
1642 end
1643
1644 val = val or key
1645 table.insert(self.keylist, tostring(key))
1646 table.insert(self.vallist, tostring(val))
1647 end
1648
1649 function MultiValue.valuelist(self, section)
1650 local val = self:cfgvalue(section)
1651
1652 if not(type(val) == "string") then
1653 return {}
1654 end
1655
1656 return luci.util.split(val, self.delimiter)
1657 end
1658
1659 function MultiValue.validate(self, val)
1660 val = (type(val) == "table") and val or {val}
1661
1662 local result
1663
1664 for i, value in ipairs(val) do
1665 if luci.util.contains(self.keylist, value) then
1666 result = result and (result .. self.delimiter .. value) or value
1667 end
1668 end
1669
1670 return result
1671 end
1672
1673
1674 StaticList = class(MultiValue)
1675
1676 function StaticList.__init__(self, ...)
1677 MultiValue.__init__(self, ...)
1678 self.cast = "table"
1679 self.valuelist = self.cfgvalue
1680
1681 if not self.override_scheme
1682 and self.map:get_scheme(self.section.sectiontype, self.option) then
1683 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1684 if self.value and vs.values and not self.override_values then
1685 for k, v in pairs(vs.values) do
1686 self:value(k, v)
1687 end
1688 end
1689 end
1690 end
1691
1692 function StaticList.validate(self, value)
1693 value = (type(value) == "table") and value or {value}
1694
1695 local valid = {}
1696 for i, v in ipairs(value) do
1697 if luci.util.contains(self.keylist, v) then
1698 table.insert(valid, v)
1699 end
1700 end
1701 return valid
1702 end
1703
1704
1705 DynamicList = class(AbstractValue)
1706
1707 function DynamicList.__init__(self, ...)
1708 AbstractValue.__init__(self, ...)
1709 self.template = "cbi/dynlist"
1710 self.cast = "table"
1711 self.keylist = {}
1712 self.vallist = {}
1713 end
1714
1715 function DynamicList.value(self, key, val)
1716 val = val or key
1717 table.insert(self.keylist, tostring(key))
1718 table.insert(self.vallist, tostring(val))
1719 end
1720
1721 function DynamicList.write(self, ...)
1722 self.map.proceed = true
1723 return AbstractValue.write(self, ...)
1724 end
1725
1726 function DynamicList.formvalue(self, section)
1727 local value = AbstractValue.formvalue(self, section)
1728 value = (type(value) == "table") and value or {value}
1729
1730 local valid = {}
1731 for i, v in ipairs(value) do
1732 if v and #v > 0
1733 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1734 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1735 table.insert(valid, v)
1736 end
1737 end
1738
1739 return valid
1740 end
1741
1742
1743 --[[
1744 TextValue - A multi-line value
1745 rows: Rows
1746 ]]--
1747 TextValue = class(AbstractValue)
1748
1749 function TextValue.__init__(self, ...)
1750 AbstractValue.__init__(self, ...)
1751 self.template = "cbi/tvalue"
1752 end
1753
1754 --[[
1755 Button
1756 ]]--
1757 Button = class(AbstractValue)
1758
1759 function Button.__init__(self, ...)
1760 AbstractValue.__init__(self, ...)
1761 self.template = "cbi/button"
1762 self.inputstyle = nil
1763 self.rmempty = true
1764 end
1765
1766
1767 FileUpload = class(AbstractValue)
1768
1769 function FileUpload.__init__(self, ...)
1770 AbstractValue.__init__(self, ...)
1771 self.template = "cbi/upload"
1772 if not self.map.upload_fields then
1773 self.map.upload_fields = { self }
1774 else
1775 self.map.upload_fields[#self.map.upload_fields+1] = self
1776 end
1777 end
1778
1779 function FileUpload.formcreated(self, section)
1780 return AbstractValue.formcreated(self, section) or
1781 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1782 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1783 end
1784
1785 function FileUpload.cfgvalue(self, section)
1786 local val = AbstractValue.cfgvalue(self, section)
1787 if val and fs.access(val) then
1788 return val
1789 end
1790 return nil
1791 end
1792
1793 function FileUpload.formvalue(self, section)
1794 local val = AbstractValue.formvalue(self, section)
1795 if val then
1796 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1797 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1798 then
1799 return val
1800 end
1801 fs.unlink(val)
1802 self.value = nil
1803 end
1804 return nil
1805 end
1806
1807 function FileUpload.remove(self, section)
1808 local val = AbstractValue.formvalue(self, section)
1809 if val and fs.access(val) then fs.unlink(val) end
1810 return AbstractValue.remove(self, section)
1811 end
1812
1813
1814 FileBrowser = class(AbstractValue)
1815
1816 function FileBrowser.__init__(self, ...)
1817 AbstractValue.__init__(self, ...)
1818 self.template = "cbi/browser"
1819 end