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