8ca9e5c8d17dce069a3d9d22477be29a4bfe9a1b
[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.cast = "string"
1269
1270 self.track_missing = false
1271 self.rmempty = true
1272 self.default = nil
1273 self.size = nil
1274 self.optional = false
1275 end
1276
1277 function AbstractValue.prepare(self)
1278 -- Use defaults from UVL
1279 if not self.override_scheme
1280 and self.map:get_scheme(self.section.sectiontype, self.option) then
1281 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1282 if self.cast == nil then
1283 self.cast = (vs.type == "list") and "list" or "string"
1284 end
1285 self.title = self.title or vs.title
1286 self.description = self.description or vs.descr
1287 if self.default == nil then
1288 self.default = vs.default
1289 end
1290
1291 if vs.depends and not self.override_dependencies then
1292 for i, deps in ipairs(vs.depends) do
1293 deps = _uvl_strip_remote_dependencies(deps)
1294 if next(deps) then
1295 self:depends(deps)
1296 end
1297 end
1298 end
1299 end
1300
1301 self.cast = self.cast or "string"
1302 end
1303
1304 -- Add a dependencie to another section field
1305 function AbstractValue.depends(self, field, value)
1306 local deps
1307 if type(field) == "string" then
1308 deps = {}
1309 deps[field] = value
1310 else
1311 deps = field
1312 end
1313
1314 table.insert(self.deps, {deps=deps, add=""})
1315 end
1316
1317 -- Generates the unique CBID
1318 function AbstractValue.cbid(self, section)
1319 return "cbid."..self.map.config.."."..section.."."..self.option
1320 end
1321
1322 -- Return whether this object should be created
1323 function AbstractValue.formcreated(self, section)
1324 local key = "cbi.opt."..self.config.."."..section
1325 return (self.map:formvalue(key) == self.option)
1326 end
1327
1328 -- Returns the formvalue for this object
1329 function AbstractValue.formvalue(self, section)
1330 return self.map:formvalue(self:cbid(section))
1331 end
1332
1333 function AbstractValue.additional(self, value)
1334 self.optional = value
1335 end
1336
1337 function AbstractValue.mandatory(self, value)
1338 self.rmempty = not value
1339 end
1340
1341 function AbstractValue.parse(self, section, novld)
1342 local fvalue = self:formvalue(section)
1343 local cvalue = self:cfgvalue(section)
1344
1345 -- If favlue and cvalue are both tables and have the same content
1346 -- make them identical
1347 if type(fvalue) == "table" and type(cvalue) == "table" then
1348 local equal = #fvalue == #cvalue
1349 if equal then
1350 for i=1, #fvalue do
1351 if cvalue[i] ~= fvalue[i] then
1352 equal = false
1353 end
1354 end
1355 end
1356 if equal then
1357 fvalue = cvalue
1358 end
1359 end
1360
1361 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1362 fvalue = self:transform(self:validate(fvalue, section))
1363 if not fvalue and not novld then
1364 if self.error then
1365 self.error[section] = "invalid"
1366 else
1367 self.error = { [section] = "invalid" }
1368 end
1369 if self.section.error then
1370 table.insert(self.section.error[section], "invalid")
1371 else
1372 self.section.error = {[section] = {"invalid"}}
1373 end
1374 self.map.save = false
1375 end
1376 if fvalue and not (fvalue == cvalue) then
1377 if self:write(section, fvalue) then
1378 -- Push events
1379 self.section.changed = true
1380 --luci.util.append(self.map.events, self.events)
1381 end
1382 end
1383 else -- Unset the UCI or error
1384 if self.rmempty or self.optional then
1385 if self:remove(section) then
1386 -- Push events
1387 self.section.changed = true
1388 --luci.util.append(self.map.events, self.events)
1389 end
1390 elseif cvalue ~= fvalue and not novld then
1391 self:write(section, fvalue or "")
1392 if self.error then
1393 self.error[section] = "missing"
1394 else
1395 self.error = { [section] = "missing" }
1396 end
1397 self.map.save = false
1398 end
1399 end
1400 end
1401
1402 -- Render if this value exists or if it is mandatory
1403 function AbstractValue.render(self, s, scope)
1404 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1405 scope = scope or {}
1406 scope.section = s
1407 scope.cbid = self:cbid(s)
1408 scope.striptags = luci.util.striptags
1409 scope.pcdata = luci.util.pcdata
1410
1411 scope.ifattr = function(cond,key,val)
1412 if cond then
1413 return string.format(
1414 ' %s="%s"', tostring(key),
1415 luci.util.pcdata(tostring( val
1416 or scope[key]
1417 or (type(self[key]) ~= "function" and self[key])
1418 or "" ))
1419 )
1420 else
1421 return ''
1422 end
1423 end
1424
1425 scope.attr = function(...)
1426 return scope.ifattr( true, ... )
1427 end
1428
1429 Node.render(self, scope)
1430 end
1431 end
1432
1433 -- Return the UCI value of this object
1434 function AbstractValue.cfgvalue(self, section)
1435 local value = self.map:get(section, self.option)
1436 if not value then
1437 return nil
1438 elseif not self.cast or self.cast == type(value) then
1439 return value
1440 elseif self.cast == "string" then
1441 if type(value) == "table" then
1442 return value[1]
1443 end
1444 elseif self.cast == "table" then
1445 return luci.util.split(value, "%s+", nil, true)
1446 end
1447 end
1448
1449 -- Validate the form value
1450 function AbstractValue.validate(self, value)
1451 return value
1452 end
1453
1454 AbstractValue.transform = AbstractValue.validate
1455
1456
1457 -- Write to UCI
1458 function AbstractValue.write(self, section, value)
1459 return self.map:set(section, self.option, value)
1460 end
1461
1462 -- Remove from UCI
1463 function AbstractValue.remove(self, section)
1464 return self.map:del(section, self.option)
1465 end
1466
1467
1468
1469
1470 --[[
1471 Value - A one-line value
1472 maxlength: The maximum length
1473 ]]--
1474 Value = class(AbstractValue)
1475
1476 function Value.__init__(self, ...)
1477 AbstractValue.__init__(self, ...)
1478 self.template = "cbi/value"
1479 self.keylist = {}
1480 self.vallist = {}
1481 end
1482
1483 function Value.value(self, key, val)
1484 val = val or key
1485 table.insert(self.keylist, tostring(key))
1486 table.insert(self.vallist, tostring(val))
1487 end
1488
1489
1490 -- DummyValue - This does nothing except being there
1491 DummyValue = class(AbstractValue)
1492
1493 function DummyValue.__init__(self, ...)
1494 AbstractValue.__init__(self, ...)
1495 self.template = "cbi/dvalue"
1496 self.value = nil
1497 end
1498
1499 function DummyValue.cfgvalue(self, section)
1500 local value
1501 if self.value then
1502 if type(self.value) == "function" then
1503 value = self:value(section)
1504 else
1505 value = self.value
1506 end
1507 else
1508 value = AbstractValue.cfgvalue(self, section)
1509 end
1510 return value
1511 end
1512
1513 function DummyValue.parse(self)
1514
1515 end
1516
1517
1518 --[[
1519 Flag - A flag being enabled or disabled
1520 ]]--
1521 Flag = class(AbstractValue)
1522
1523 function Flag.__init__(self, ...)
1524 AbstractValue.__init__(self, ...)
1525 self.template = "cbi/fvalue"
1526
1527 self.enabled = "1"
1528 self.disabled = "0"
1529 end
1530
1531 -- A flag can only have two states: set or unset
1532 function Flag.parse(self, section)
1533 local fvalue = self:formvalue(section)
1534
1535 if fvalue then
1536 fvalue = self.enabled
1537 else
1538 fvalue = self.disabled
1539 end
1540
1541 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1542 if not(fvalue == self:cfgvalue(section)) then
1543 self:write(section, fvalue)
1544 end
1545 else
1546 self:remove(section)
1547 end
1548 end
1549
1550
1551
1552 --[[
1553 ListValue - A one-line value predefined in a list
1554 widget: The widget that will be used (select, radio)
1555 ]]--
1556 ListValue = class(AbstractValue)
1557
1558 function ListValue.__init__(self, ...)
1559 AbstractValue.__init__(self, ...)
1560 self.template = "cbi/lvalue"
1561
1562 self.keylist = {}
1563 self.vallist = {}
1564 self.size = 1
1565 self.widget = "select"
1566 end
1567
1568 function ListValue.prepare(self, ...)
1569 AbstractValue.prepare(self, ...)
1570 if not self.override_scheme
1571 and self.map:get_scheme(self.section.sectiontype, self.option) then
1572 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1573 if self.value and vs.valuelist and not self.override_values then
1574 for k, v in ipairs(vs.valuelist) do
1575 local deps = {}
1576 if not self.override_dependencies
1577 and vs.enum_depends and vs.enum_depends[v.value] then
1578 for i, dep in ipairs(vs.enum_depends[v.value]) do
1579 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1580 end
1581 end
1582 self:value(v.value, v.title or v.value, unpack(deps))
1583 end
1584 end
1585 end
1586 end
1587
1588 function ListValue.value(self, key, val, ...)
1589 if luci.util.contains(self.keylist, key) then
1590 return
1591 end
1592
1593 val = val or key
1594 table.insert(self.keylist, tostring(key))
1595 table.insert(self.vallist, tostring(val))
1596
1597 for i, deps in ipairs({...}) do
1598 table.insert(self.deps, {add = "-"..key, deps=deps})
1599 end
1600 end
1601
1602 function ListValue.validate(self, val)
1603 if luci.util.contains(self.keylist, val) then
1604 return val
1605 else
1606 return nil
1607 end
1608 end
1609
1610
1611
1612 --[[
1613 MultiValue - Multiple delimited values
1614 widget: The widget that will be used (select, checkbox)
1615 delimiter: The delimiter that will separate the values (default: " ")
1616 ]]--
1617 MultiValue = class(AbstractValue)
1618
1619 function MultiValue.__init__(self, ...)
1620 AbstractValue.__init__(self, ...)
1621 self.template = "cbi/mvalue"
1622
1623 self.keylist = {}
1624 self.vallist = {}
1625
1626 self.widget = "checkbox"
1627 self.delimiter = " "
1628 end
1629
1630 function MultiValue.render(self, ...)
1631 if self.widget == "select" and not self.size then
1632 self.size = #self.vallist
1633 end
1634
1635 AbstractValue.render(self, ...)
1636 end
1637
1638 function MultiValue.value(self, key, val)
1639 if luci.util.contains(self.keylist, key) then
1640 return
1641 end
1642
1643 val = val or key
1644 table.insert(self.keylist, tostring(key))
1645 table.insert(self.vallist, tostring(val))
1646 end
1647
1648 function MultiValue.valuelist(self, section)
1649 local val = self:cfgvalue(section)
1650
1651 if not(type(val) == "string") then
1652 return {}
1653 end
1654
1655 return luci.util.split(val, self.delimiter)
1656 end
1657
1658 function MultiValue.validate(self, val)
1659 val = (type(val) == "table") and val or {val}
1660
1661 local result
1662
1663 for i, value in ipairs(val) do
1664 if luci.util.contains(self.keylist, value) then
1665 result = result and (result .. self.delimiter .. value) or value
1666 end
1667 end
1668
1669 return result
1670 end
1671
1672
1673 StaticList = class(MultiValue)
1674
1675 function StaticList.__init__(self, ...)
1676 MultiValue.__init__(self, ...)
1677 self.cast = "table"
1678 self.valuelist = self.cfgvalue
1679
1680 if not self.override_scheme
1681 and self.map:get_scheme(self.section.sectiontype, self.option) then
1682 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1683 if self.value and vs.values and not self.override_values then
1684 for k, v in pairs(vs.values) do
1685 self:value(k, v)
1686 end
1687 end
1688 end
1689 end
1690
1691 function StaticList.validate(self, value)
1692 value = (type(value) == "table") and value or {value}
1693
1694 local valid = {}
1695 for i, v in ipairs(value) do
1696 if luci.util.contains(self.keylist, v) then
1697 table.insert(valid, v)
1698 end
1699 end
1700 return valid
1701 end
1702
1703
1704 DynamicList = class(AbstractValue)
1705
1706 function DynamicList.__init__(self, ...)
1707 AbstractValue.__init__(self, ...)
1708 self.template = "cbi/dynlist"
1709 self.cast = "table"
1710 self.keylist = {}
1711 self.vallist = {}
1712 end
1713
1714 function DynamicList.value(self, key, val)
1715 val = val or key
1716 table.insert(self.keylist, tostring(key))
1717 table.insert(self.vallist, tostring(val))
1718 end
1719
1720 function DynamicList.write(self, ...)
1721 self.map.proceed = true
1722 return AbstractValue.write(self, ...)
1723 end
1724
1725 function DynamicList.formvalue(self, section)
1726 local value = AbstractValue.formvalue(self, section)
1727 value = (type(value) == "table") and value or {value}
1728
1729 local valid = {}
1730 for i, v in ipairs(value) do
1731 if v and #v > 0
1732 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1733 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1734 table.insert(valid, v)
1735 end
1736 end
1737
1738 return valid
1739 end
1740
1741
1742 --[[
1743 TextValue - A multi-line value
1744 rows: Rows
1745 ]]--
1746 TextValue = class(AbstractValue)
1747
1748 function TextValue.__init__(self, ...)
1749 AbstractValue.__init__(self, ...)
1750 self.template = "cbi/tvalue"
1751 end
1752
1753 --[[
1754 Button
1755 ]]--
1756 Button = class(AbstractValue)
1757
1758 function Button.__init__(self, ...)
1759 AbstractValue.__init__(self, ...)
1760 self.template = "cbi/button"
1761 self.inputstyle = nil
1762 self.rmempty = true
1763 end
1764
1765
1766 FileUpload = class(AbstractValue)
1767
1768 function FileUpload.__init__(self, ...)
1769 AbstractValue.__init__(self, ...)
1770 self.template = "cbi/upload"
1771 if not self.map.upload_fields then
1772 self.map.upload_fields = { self }
1773 else
1774 self.map.upload_fields[#self.map.upload_fields+1] = self
1775 end
1776 end
1777
1778 function FileUpload.formcreated(self, section)
1779 return AbstractValue.formcreated(self, section) or
1780 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1781 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1782 end
1783
1784 function FileUpload.cfgvalue(self, section)
1785 local val = AbstractValue.cfgvalue(self, section)
1786 if val and fs.access(val) then
1787 return val
1788 end
1789 return nil
1790 end
1791
1792 function FileUpload.formvalue(self, section)
1793 local val = AbstractValue.formvalue(self, section)
1794 if val then
1795 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1796 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1797 then
1798 return val
1799 end
1800 fs.unlink(val)
1801 self.value = nil
1802 end
1803 return nil
1804 end
1805
1806 function FileUpload.remove(self, section)
1807 local val = AbstractValue.formvalue(self, section)
1808 if val and fs.access(val) then fs.unlink(val) end
1809 return AbstractValue.remove(self, section)
1810 end
1811
1812
1813 FileBrowser = class(AbstractValue)
1814
1815 function FileBrowser.__init__(self, ...)
1816 AbstractValue.__init__(self, ...)
1817 self.template = "cbi/browser"
1818 end