9a88a2b366446bab5cecc1846597b6dcee41d46c
[project/luci.git] / libs / cbi / luasrc / cbi.lua
1 --[[
2 LuCI - Configuration Bind Interface
3
4 Description:
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
7
8 FileId:
9 $Id$
10
11 License:
12 Copyright 2008 Steven Barth <steven@midlink.org>
13
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
17
18 http://www.apache.org/licenses/LICENSE-2.0
19
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
25
26 ]]--
27 module("luci.cbi", package.seeall)
28
29 require("luci.template")
30 local util = require("luci.util")
31 require("luci.http")
32 require("luci.uvl")
33
34
35 --local event = require "luci.sys.event"
36 local fs = require("nixio.fs")
37 local uci = require("luci.model.uci")
38 local class = util.class
39 local instanceof = util.instanceof
40
41 FORM_NODATA = 0
42 FORM_PROCEED = 0
43 FORM_VALID = 1
44 FORM_DONE = 1
45 FORM_INVALID = -1
46 FORM_CHANGED = 2
47 FORM_SKIP = 4
48
49 AUTO = true
50
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
53
54 -- Loads a CBI map from given file, creating an environment and returns it
55 function load(cbimap, ...)
56 local fs = require "nixio.fs"
57 local i18n = require "luci.i18n"
58 require("luci.config")
59 require("luci.util")
60
61 local upldir = "/lib/uci/upload/"
62 local cbidir = luci.util.libpath() .. "/model/cbi/"
63 local func, err
64
65 if fs.access(cbimap) then
66 func, err = loadfile(cbimap)
67 elseif fs.access(cbidir..cbimap..".lua") then
68 func, err = loadfile(cbidir..cbimap..".lua")
69 elseif fs.access(cbidir..cbimap..".lua.gz") then
70 func, err = loadfile(cbidir..cbimap..".lua.gz")
71 else
72 func, err = nil, "Model '" .. cbimap .. "' not found!"
73 end
74
75 assert(func, err)
76
77 luci.i18n.loadc("cbi")
78 luci.i18n.loadc("uvl")
79
80 local env = {
81 translate=i18n.translate,
82 translatef=i18n.translatef,
83 arg={...}
84 }
85
86 setfenv(func, setmetatable(env, {__index =
87 function(tbl, key)
88 return rawget(tbl, key) or _M[key] or _G[key]
89 end}))
90
91 local maps = { func() }
92 local uploads = { }
93 local has_upload = false
94
95 for i, map in ipairs(maps) do
96 if not instanceof(map, Node) then
97 error("CBI map returns no valid map object!")
98 return nil
99 else
100 map:prepare()
101 if map.upload_fields then
102 has_upload = true
103 for _, field in ipairs(map.upload_fields) do
104 uploads[
105 field.config .. '.' ..
106 field.section.sectiontype .. '.' ..
107 field.option
108 ] = true
109 end
110 end
111 end
112 end
113
114 if has_upload then
115 local uci = luci.model.uci.cursor()
116 local prm = luci.http.context.request.message.params
117 local fd, cbid
118
119 luci.http.setfilehandler(
120 function( field, chunk, eof )
121 if not field then return end
122 if field.name and not cbid then
123 local c, s, o = field.name:gmatch(
124 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
125 )()
126
127 if c and s and o then
128 local t = uci:get( c, s )
129 if t and uploads[c.."."..t.."."..o] then
130 local path = upldir .. field.name
131 fd = io.open(path, "w")
132 if fd then
133 cbid = field.name
134 prm[cbid] = path
135 end
136 end
137 end
138 end
139
140 if field.name == cbid and fd then
141 fd:write(chunk)
142 end
143
144 if eof and fd then
145 fd:close()
146 fd = nil
147 cbid = nil
148 end
149 end
150 )
151 end
152
153 return maps
154 end
155
156 local function _uvl_validate_section(node, name)
157 local co = node.map:get()
158
159 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
160 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
161
162 local function tag_fields(e)
163 if e.option and node.fields[e.option] then
164 if node.fields[e.option].error then
165 node.fields[e.option].error[name] = e
166 else
167 node.fields[e.option].error = { [name] = e }
168 end
169 elseif e.childs then
170 for _, c in ipairs(e.childs) do tag_fields(c) end
171 end
172 end
173
174 local function tag_section(e)
175 local s = { }
176 for _, c in ipairs(e.childs or { e }) do
177 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
178 table.insert( s, c.childs[1]:string() )
179 else
180 table.insert( s, c:string() )
181 end
182 end
183 if #s > 0 then
184 if node.error then
185 node.error[name] = s
186 else
187 node.error = { [name] = s }
188 end
189 end
190 end
191
192 local stat, err = node.map.validator:validate_section(node.config, name, co)
193 if err then
194 node.map.save = false
195 tag_fields(err)
196 tag_section(err)
197 end
198
199 end
200
201 local function _uvl_strip_remote_dependencies(deps)
202 local clean = {}
203
204 for k, v in pairs(deps) do
205 k = k:gsub("%$config%.%$section%.", "")
206 if k:match("^[%w_]+$") and type(v) == "string" then
207 clean[k] = v
208 end
209 end
210
211 return clean
212 end
213
214
215 -- Node pseudo abstract class
216 Node = class()
217
218 function Node.__init__(self, title, description)
219 self.children = {}
220 self.title = title or ""
221 self.description = description or ""
222 self.template = "cbi/node"
223 end
224
225 -- hook helper
226 function Node._run_hooks(self, ...)
227 local f
228 local r = false
229 for _, f in ipairs(arg) do
230 if type(self[f]) == "function" then
231 self[f](self)
232 r = true
233 end
234 end
235 return r
236 end
237
238 -- Prepare nodes
239 function Node.prepare(self, ...)
240 for k, child in ipairs(self.children) do
241 child:prepare(...)
242 end
243 end
244
245 -- Append child nodes
246 function Node.append(self, obj)
247 table.insert(self.children, obj)
248 end
249
250 -- Parse this node and its children
251 function Node.parse(self, ...)
252 for k, child in ipairs(self.children) do
253 child:parse(...)
254 end
255 end
256
257 -- Render this node
258 function Node.render(self, scope)
259 scope = scope or {}
260 scope.self = self
261
262 luci.template.render(self.template, scope)
263 end
264
265 -- Render the children
266 function Node.render_children(self, ...)
267 for k, node in ipairs(self.children) do
268 node:render(...)
269 end
270 end
271
272
273 --[[
274 A simple template element
275 ]]--
276 Template = class(Node)
277
278 function Template.__init__(self, template)
279 Node.__init__(self)
280 self.template = template
281 end
282
283 function Template.render(self)
284 luci.template.render(self.template, {self=self})
285 end
286
287 function Template.parse(self, readinput)
288 self.readinput = (readinput ~= false)
289 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
290 end
291
292
293 --[[
294 Map - A map describing a configuration file
295 ]]--
296 Map = class(Node)
297
298 function Map.__init__(self, config, ...)
299 Node.__init__(self, ...)
300
301 self.config = config
302 self.parsechain = {self.config}
303 self.template = "cbi/map"
304 self.apply_on_parse = nil
305 self.readinput = true
306 self.proceed = false
307 self.flow = {}
308
309 self.uci = uci.cursor()
310 self.save = true
311
312 self.changed = false
313
314 if not self.uci:load(self.config) then
315 error("Unable to read UCI data: " .. self.config)
316 end
317
318 self.validator = luci.uvl.UVL()
319 self.scheme = self.validator:get_scheme(self.config)
320 end
321
322 function Map.formvalue(self, key)
323 return self.readinput and luci.http.formvalue(key)
324 end
325
326 function Map.formvaluetable(self, key)
327 return self.readinput and luci.http.formvaluetable(key) or {}
328 end
329
330 function Map.get_scheme(self, sectiontype, option)
331 if not option then
332 return self.scheme and self.scheme.sections[sectiontype]
333 else
334 return self.scheme and self.scheme.variables[sectiontype]
335 and self.scheme.variables[sectiontype][option]
336 end
337 end
338
339 function Map.submitstate(self)
340 return self:formvalue("cbi.submit")
341 end
342
343 -- Chain foreign config
344 function Map.chain(self, config)
345 table.insert(self.parsechain, config)
346 end
347
348 function Map.state_handler(self, state)
349 return state
350 end
351
352 -- Use optimized UCI writing
353 function Map.parse(self, readinput, ...)
354 self.readinput = (readinput ~= false)
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:submitstate() then
676 Node.parse(self, 1, ...)
677 end
678
679 local valid = true
680 for k, j in ipairs(self.children) do
681 for i, v in ipairs(j.children) do
682 valid = valid
683 and (not v.tag_missing or not v.tag_missing[1])
684 and (not v.tag_invalid or not v.tag_invalid[1])
685 and (not v.error)
686 end
687 end
688
689 local state =
690 not self:submitstate() and FORM_NODATA
691 or valid and FORM_VALID
692 or FORM_INVALID
693
694 self.dorender = not self.handle
695 if self.handle then
696 local nrender, nstate = self:handle(state, self.data)
697 self.dorender = self.dorender or (nrender ~= false)
698 state = nstate or state
699 end
700 return state
701 end
702
703 function SimpleForm.render(self, ...)
704 if self.dorender then
705 Node.render(self, ...)
706 end
707 end
708
709 function SimpleForm.submitstate(self)
710 return self:formvalue("cbi.submit")
711 end
712
713 function SimpleForm.section(self, class, ...)
714 if instanceof(class, AbstractSection) then
715 local obj = class(self, ...)
716 self:append(obj)
717 return obj
718 else
719 error("class must be a descendent of AbstractSection")
720 end
721 end
722
723 -- Creates a child field
724 function SimpleForm.field(self, class, ...)
725 local section
726 for k, v in ipairs(self.children) do
727 if instanceof(v, SimpleSection) then
728 section = v
729 break
730 end
731 end
732 if not section then
733 section = self:section(SimpleSection)
734 end
735
736 if instanceof(class, AbstractValue) then
737 local obj = class(self, section, ...)
738 obj.track_missing = true
739 section:append(obj)
740 return obj
741 else
742 error("class must be a descendent of AbstractValue")
743 end
744 end
745
746 function SimpleForm.set(self, section, option, value)
747 self.data[option] = value
748 end
749
750
751 function SimpleForm.del(self, section, option)
752 self.data[option] = nil
753 end
754
755
756 function SimpleForm.get(self, section, option)
757 return self.data[option]
758 end
759
760
761 function SimpleForm.get_scheme()
762 return nil
763 end
764
765
766 Form = class(SimpleForm)
767
768 function Form.__init__(self, ...)
769 SimpleForm.__init__(self, ...)
770 self.embedded = true
771 end
772
773
774 --[[
775 AbstractSection
776 ]]--
777 AbstractSection = class(Node)
778
779 function AbstractSection.__init__(self, map, sectiontype, ...)
780 Node.__init__(self, ...)
781 self.sectiontype = sectiontype
782 self.map = map
783 self.config = map.config
784 self.optionals = {}
785 self.defaults = {}
786 self.fields = {}
787 self.tag_error = {}
788 self.tag_invalid = {}
789 self.tag_deperror = {}
790 self.changed = false
791
792 self.optional = true
793 self.addremove = false
794 self.dynamic = false
795 end
796
797 -- Define a tab for the section
798 function AbstractSection.tab(self, tab, title, desc)
799 self.tabs = self.tabs or { }
800 self.tab_names = self.tab_names or { }
801
802 self.tab_names[#self.tab_names+1] = tab
803 self.tabs[tab] = {
804 title = title,
805 description = desc,
806 childs = { }
807 }
808 end
809
810 -- Appends a new option
811 function AbstractSection.option(self, class, option, ...)
812 -- Autodetect from UVL
813 if class == true and self.map:get_scheme(self.sectiontype, option) then
814 local vs = self.map:get_scheme(self.sectiontype, option)
815 if vs.type == "boolean" then
816 class = Flag
817 elseif vs.type == "list" then
818 class = DynamicList
819 elseif vs.type == "enum" or vs.type == "reference" then
820 class = ListValue
821 else
822 class = Value
823 end
824 end
825
826 if instanceof(class, AbstractValue) then
827 local obj = class(self.map, self, option, ...)
828 self:append(obj)
829 self.fields[option] = obj
830 return obj
831 elseif class == true then
832 error("No valid class was given and autodetection failed.")
833 else
834 error("class must be a descendant of AbstractValue")
835 end
836 end
837
838 -- Appends a new tabbed option
839 function AbstractSection.taboption(self, tab, ...)
840
841 assert(tab and self.tabs and self.tabs[tab],
842 "Cannot assign option to not existing tab %q" % tostring(tab))
843
844 local l = self.tabs[tab].childs
845 local o = AbstractSection.option(self, ...)
846
847 if o then l[#l+1] = o end
848
849 return o
850 end
851
852 -- Render a single tab
853 function AbstractSection.render_tab(self, tab, ...)
854
855 assert(tab and self.tabs and self.tabs[tab],
856 "Cannot render not existing tab %q" % tostring(tab))
857
858 for _, node in ipairs(self.tabs[tab].childs) do
859 node:render(...)
860 end
861 end
862
863 -- Parse optional options
864 function AbstractSection.parse_optionals(self, section)
865 if not self.optional then
866 return
867 end
868
869 self.optionals[section] = {}
870
871 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
872 for k,v in ipairs(self.children) do
873 if v.optional and not v:cfgvalue(section) then
874 if field == v.option then
875 field = nil
876 self.map.proceed = true
877 else
878 table.insert(self.optionals[section], v)
879 end
880 end
881 end
882
883 if field and #field > 0 and self.dynamic then
884 self:add_dynamic(field)
885 end
886 end
887
888 -- Add a dynamic option
889 function AbstractSection.add_dynamic(self, field, optional)
890 local o = self:option(Value, field, field)
891 o.optional = optional
892 end
893
894 -- Parse all dynamic options
895 function AbstractSection.parse_dynamic(self, section)
896 if not self.dynamic then
897 return
898 end
899
900 local arr = luci.util.clone(self:cfgvalue(section))
901 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
902 for k, v in pairs(form) do
903 arr[k] = v
904 end
905
906 for key,val in pairs(arr) do
907 local create = true
908
909 for i,c in ipairs(self.children) do
910 if c.option == key then
911 create = false
912 end
913 end
914
915 if create and key:sub(1, 1) ~= "." then
916 self.map.proceed = true
917 self:add_dynamic(key, true)
918 end
919 end
920 end
921
922 -- Returns the section's UCI table
923 function AbstractSection.cfgvalue(self, section)
924 return self.map:get(section)
925 end
926
927 -- Push events
928 function AbstractSection.push_events(self)
929 --luci.util.append(self.map.events, self.events)
930 self.map.changed = true
931 end
932
933 -- Removes the section
934 function AbstractSection.remove(self, section)
935 self.map.proceed = true
936 return self.map:del(section)
937 end
938
939 -- Creates the section
940 function AbstractSection.create(self, section)
941 local stat
942
943 if section then
944 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
945 else
946 section = self.map:add(self.sectiontype)
947 stat = section
948 end
949
950 if stat then
951 for k,v in pairs(self.children) do
952 if v.default then
953 self.map:set(section, v.option, v.default)
954 end
955 end
956
957 for k,v in pairs(self.defaults) do
958 self.map:set(section, k, v)
959 end
960 end
961
962 self.map.proceed = true
963
964 return stat
965 end
966
967
968 SimpleSection = class(AbstractSection)
969
970 function SimpleSection.__init__(self, form, ...)
971 AbstractSection.__init__(self, form, nil, ...)
972 self.template = "cbi/nullsection"
973 end
974
975
976 Table = class(AbstractSection)
977
978 function Table.__init__(self, form, data, ...)
979 local datasource = {}
980 local tself = self
981 datasource.config = "table"
982 self.data = data or {}
983
984 datasource.formvalue = Map.formvalue
985 datasource.formvaluetable = Map.formvaluetable
986 datasource.readinput = true
987
988 function datasource.get(self, section, option)
989 return tself.data[section] and tself.data[section][option]
990 end
991
992 function datasource.submitstate(self)
993 return Map.formvalue(self, "cbi.submit")
994 end
995
996 function datasource.del(...)
997 return true
998 end
999
1000 function datasource.get_scheme()
1001 return nil
1002 end
1003
1004 AbstractSection.__init__(self, datasource, "table", ...)
1005 self.template = "cbi/tblsection"
1006 self.rowcolors = true
1007 self.anonymous = true
1008 end
1009
1010 function Table.parse(self, readinput)
1011 self.map.readinput = (readinput ~= false)
1012 for i, k in ipairs(self:cfgsections()) do
1013 if self.map:submitstate() then
1014 Node.parse(self, k)
1015 end
1016 end
1017 end
1018
1019 function Table.cfgsections(self)
1020 local sections = {}
1021
1022 for i, v in luci.util.kspairs(self.data) do
1023 table.insert(sections, i)
1024 end
1025
1026 return sections
1027 end
1028
1029 function Table.update(self, data)
1030 self.data = data
1031 end
1032
1033
1034
1035 --[[
1036 NamedSection - A fixed configuration section defined by its name
1037 ]]--
1038 NamedSection = class(AbstractSection)
1039
1040 function NamedSection.__init__(self, map, section, stype, ...)
1041 AbstractSection.__init__(self, map, stype, ...)
1042
1043 -- Defaults
1044 self.addremove = false
1045
1046 -- Use defaults from UVL
1047 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1048 local vs = self.map:get_scheme(self.sectiontype)
1049 self.addremove = not vs.unique and not vs.required
1050 self.dynamic = vs.dynamic
1051 self.title = self.title or vs.title
1052 self.description = self.description or vs.descr
1053 end
1054
1055 self.template = "cbi/nsection"
1056 self.section = section
1057 end
1058
1059 function NamedSection.parse(self, novld)
1060 local s = self.section
1061 local active = self:cfgvalue(s)
1062
1063 if self.addremove then
1064 local path = self.config.."."..s
1065 if active then -- Remove the section
1066 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1067 self:push_events()
1068 return
1069 end
1070 else -- Create and apply default values
1071 if self.map:formvalue("cbi.cns."..path) then
1072 self:create(s)
1073 return
1074 end
1075 end
1076 end
1077
1078 if active then
1079 AbstractSection.parse_dynamic(self, s)
1080 if self.map:submitstate() then
1081 Node.parse(self, s)
1082
1083 if not novld and not self.override_scheme and self.map.scheme then
1084 _uvl_validate_section(self, s)
1085 end
1086 end
1087 AbstractSection.parse_optionals(self, s)
1088
1089 if self.changed then
1090 self:push_events()
1091 end
1092 end
1093 end
1094
1095
1096 --[[
1097 TypedSection - A (set of) configuration section(s) defined by the type
1098 addremove: Defines whether the user can add/remove sections of this type
1099 anonymous: Allow creating anonymous sections
1100 validate: a validation function returning nil if the section is invalid
1101 ]]--
1102 TypedSection = class(AbstractSection)
1103
1104 function TypedSection.__init__(self, map, type, ...)
1105 AbstractSection.__init__(self, map, type, ...)
1106
1107 self.template = "cbi/tsection"
1108 self.deps = {}
1109 self.anonymous = false
1110
1111 -- Use defaults from UVL
1112 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1113 local vs = self.map:get_scheme(self.sectiontype)
1114 self.addremove = not vs.unique and not vs.required
1115 self.dynamic = vs.dynamic
1116 self.anonymous = not vs.named
1117 self.title = self.title or vs.title
1118 self.description = self.description or vs.descr
1119 end
1120 end
1121
1122 -- Return all matching UCI sections for this TypedSection
1123 function TypedSection.cfgsections(self)
1124 local sections = {}
1125 self.map.uci:foreach(self.map.config, self.sectiontype,
1126 function (section)
1127 if self:checkscope(section[".name"]) then
1128 table.insert(sections, section[".name"])
1129 end
1130 end)
1131
1132 return sections
1133 end
1134
1135 -- Limits scope to sections that have certain option => value pairs
1136 function TypedSection.depends(self, option, value)
1137 table.insert(self.deps, {option=option, value=value})
1138 end
1139
1140 function TypedSection.parse(self, novld)
1141 if self.addremove then
1142 -- Remove
1143 local crval = REMOVE_PREFIX .. self.config
1144 local name = self.map:formvaluetable(crval)
1145 for k,v in pairs(name) do
1146 if k:sub(-2) == ".x" then
1147 k = k:sub(1, #k - 2)
1148 end
1149 if self:cfgvalue(k) and self:checkscope(k) then
1150 self:remove(k)
1151 end
1152 end
1153 end
1154
1155 local co
1156 for i, k in ipairs(self:cfgsections()) do
1157 AbstractSection.parse_dynamic(self, k)
1158 if self.map:submitstate() then
1159 Node.parse(self, k, novld)
1160
1161 if not novld and not self.override_scheme and self.map.scheme then
1162 _uvl_validate_section(self, k)
1163 end
1164 end
1165 AbstractSection.parse_optionals(self, k)
1166 end
1167
1168 if self.addremove then
1169 -- Create
1170 local created
1171 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1172 local name = self.map:formvalue(crval)
1173 if self.anonymous then
1174 if name then
1175 created = self:create()
1176 end
1177 else
1178 if name then
1179 -- Ignore if it already exists
1180 if self:cfgvalue(name) then
1181 name = nil;
1182 end
1183
1184 name = self:checkscope(name)
1185
1186 if not name then
1187 self.err_invalid = true
1188 end
1189
1190 if name and #name > 0 then
1191 created = self:create(name) and name
1192 if not created then
1193 self.invalid_cts = true
1194 end
1195 end
1196 end
1197 end
1198
1199 if created then
1200 AbstractSection.parse_optionals(self, created)
1201 end
1202 end
1203
1204 if created or self.changed then
1205 self:push_events()
1206 end
1207 end
1208
1209 -- Verifies scope of sections
1210 function TypedSection.checkscope(self, section)
1211 -- Check if we are not excluded
1212 if self.filter and not self:filter(section) then
1213 return nil
1214 end
1215
1216 -- Check if at least one dependency is met
1217 if #self.deps > 0 and self:cfgvalue(section) then
1218 local stat = false
1219
1220 for k, v in ipairs(self.deps) do
1221 if self:cfgvalue(section)[v.option] == v.value then
1222 stat = true
1223 end
1224 end
1225
1226 if not stat then
1227 return nil
1228 end
1229 end
1230
1231 return self:validate(section)
1232 end
1233
1234
1235 -- Dummy validate function
1236 function TypedSection.validate(self, section)
1237 return section
1238 end
1239
1240
1241 --[[
1242 AbstractValue - An abstract Value Type
1243 null: Value can be empty
1244 valid: A function returning the value if it is valid otherwise nil
1245 depends: A table of option => value pairs of which one must be true
1246 default: The default value
1247 size: The size of the input fields
1248 rmempty: Unset value if empty
1249 optional: This value is optional (see AbstractSection.optionals)
1250 ]]--
1251 AbstractValue = class(Node)
1252
1253 function AbstractValue.__init__(self, map, section, option, ...)
1254 Node.__init__(self, ...)
1255 self.section = section
1256 self.option = option
1257 self.map = map
1258 self.config = map.config
1259 self.tag_invalid = {}
1260 self.tag_missing = {}
1261 self.tag_reqerror = {}
1262 self.tag_error = {}
1263 self.deps = {}
1264 --self.cast = "string"
1265
1266 self.track_missing = false
1267 self.rmempty = true
1268 self.default = nil
1269 self.size = nil
1270 self.optional = false
1271 end
1272
1273 function AbstractValue.prepare(self)
1274 -- Use defaults from UVL
1275 if not self.override_scheme
1276 and self.map:get_scheme(self.section.sectiontype, self.option) then
1277 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1278 if self.cast == nil then
1279 self.cast = (vs.type == "list") and "list" or "string"
1280 end
1281 self.title = self.title or vs.title
1282 self.description = self.description or vs.descr
1283 if self.default == nil then
1284 self.default = vs.default
1285 end
1286
1287 if vs.depends and not self.override_dependencies then
1288 for i, deps in ipairs(vs.depends) do
1289 deps = _uvl_strip_remote_dependencies(deps)
1290 if next(deps) then
1291 self:depends(deps)
1292 end
1293 end
1294 end
1295 end
1296
1297 self.cast = self.cast or "string"
1298 end
1299
1300 -- Add a dependencie to another section field
1301 function AbstractValue.depends(self, field, value)
1302 local deps
1303 if type(field) == "string" then
1304 deps = {}
1305 deps[field] = value
1306 else
1307 deps = field
1308 end
1309
1310 table.insert(self.deps, {deps=deps, add=""})
1311 end
1312
1313 -- Generates the unique CBID
1314 function AbstractValue.cbid(self, section)
1315 return "cbid."..self.map.config.."."..section.."."..self.option
1316 end
1317
1318 -- Return whether this object should be created
1319 function AbstractValue.formcreated(self, section)
1320 local key = "cbi.opt."..self.config.."."..section
1321 return (self.map:formvalue(key) == self.option)
1322 end
1323
1324 -- Returns the formvalue for this object
1325 function AbstractValue.formvalue(self, section)
1326 return self.map:formvalue(self:cbid(section))
1327 end
1328
1329 function AbstractValue.additional(self, value)
1330 self.optional = value
1331 end
1332
1333 function AbstractValue.mandatory(self, value)
1334 self.rmempty = not value
1335 end
1336
1337 function AbstractValue.parse(self, section, novld)
1338 local fvalue = self:formvalue(section)
1339 local cvalue = self:cfgvalue(section)
1340
1341 -- If favlue and cvalue are both tables and have the same content
1342 -- make them identical
1343 if type(fvalue) == "table" and type(cvalue) == "table" then
1344 local equal = #fvalue == #cvalue
1345 if equal then
1346 for i=1, #fvalue do
1347 if cvalue[i] ~= fvalue[i] then
1348 equal = false
1349 end
1350 end
1351 end
1352 if equal then
1353 fvalue = cvalue
1354 end
1355 end
1356
1357 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1358 fvalue = self:transform(self:validate(fvalue, section))
1359 if not fvalue and not novld then
1360 if self.error then
1361 self.error[section] = "invalid"
1362 else
1363 self.error = { [section] = "invalid" }
1364 end
1365 if self.section.error then
1366 table.insert(self.section.error[section], "invalid")
1367 else
1368 self.section.error = {[section] = {"invalid"}}
1369 end
1370 self.map.save = false
1371 end
1372 if fvalue and not (fvalue == cvalue) then
1373 if self:write(section, fvalue) then
1374 -- Push events
1375 self.section.changed = true
1376 --luci.util.append(self.map.events, self.events)
1377 end
1378 end
1379 else -- Unset the UCI or error
1380 if self.rmempty or self.optional then
1381 if self:remove(section) then
1382 -- Push events
1383 self.section.changed = true
1384 --luci.util.append(self.map.events, self.events)
1385 end
1386 elseif cvalue ~= fvalue and not novld then
1387 self:write(section, fvalue or "")
1388 if self.error then
1389 self.error[section] = "missing"
1390 else
1391 self.error = { [section] = "missing" }
1392 end
1393 self.map.save = false
1394 end
1395 end
1396 end
1397
1398 -- Render if this value exists or if it is mandatory
1399 function AbstractValue.render(self, s, scope)
1400 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1401 scope = scope or {}
1402 scope.section = s
1403 scope.cbid = self:cbid(s)
1404 scope.striptags = luci.util.striptags
1405 scope.pcdata = luci.util.pcdata
1406
1407 scope.ifattr = function(cond,key,val)
1408 if cond then
1409 return string.format(
1410 ' %s="%s"', tostring(key),
1411 luci.util.pcdata(tostring( val
1412 or scope[key]
1413 or (type(self[key]) ~= "function" and self[key])
1414 or "" ))
1415 )
1416 else
1417 return ''
1418 end
1419 end
1420
1421 scope.attr = function(...)
1422 return scope.ifattr( true, ... )
1423 end
1424
1425 Node.render(self, scope)
1426 end
1427 end
1428
1429 -- Return the UCI value of this object
1430 function AbstractValue.cfgvalue(self, section)
1431 local value = self.map:get(section, self.option)
1432 if not value then
1433 return nil
1434 elseif not self.cast or self.cast == type(value) then
1435 return value
1436 elseif self.cast == "string" then
1437 if type(value) == "table" then
1438 return value[1]
1439 end
1440 elseif self.cast == "table" then
1441 return luci.util.split(value, "%s+", nil, true)
1442 end
1443 end
1444
1445 -- Validate the form value
1446 function AbstractValue.validate(self, value)
1447 return value
1448 end
1449
1450 AbstractValue.transform = AbstractValue.validate
1451
1452
1453 -- Write to UCI
1454 function AbstractValue.write(self, section, value)
1455 return self.map:set(section, self.option, value)
1456 end
1457
1458 -- Remove from UCI
1459 function AbstractValue.remove(self, section)
1460 return self.map:del(section, self.option)
1461 end
1462
1463
1464
1465
1466 --[[
1467 Value - A one-line value
1468 maxlength: The maximum length
1469 ]]--
1470 Value = class(AbstractValue)
1471
1472 function Value.__init__(self, ...)
1473 AbstractValue.__init__(self, ...)
1474 self.template = "cbi/value"
1475 self.keylist = {}
1476 self.vallist = {}
1477 end
1478
1479 function Value.value(self, key, val)
1480 val = val or key
1481 table.insert(self.keylist, tostring(key))
1482 table.insert(self.vallist, tostring(val))
1483 end
1484
1485
1486 -- DummyValue - This does nothing except being there
1487 DummyValue = class(AbstractValue)
1488
1489 function DummyValue.__init__(self, ...)
1490 AbstractValue.__init__(self, ...)
1491 self.template = "cbi/dvalue"
1492 self.value = nil
1493 end
1494
1495 function DummyValue.cfgvalue(self, section)
1496 local value
1497 if self.value then
1498 if type(self.value) == "function" then
1499 value = self:value(section)
1500 else
1501 value = self.value
1502 end
1503 else
1504 value = AbstractValue.cfgvalue(self, section)
1505 end
1506 return value
1507 end
1508
1509 function DummyValue.parse(self)
1510
1511 end
1512
1513
1514 --[[
1515 Flag - A flag being enabled or disabled
1516 ]]--
1517 Flag = class(AbstractValue)
1518
1519 function Flag.__init__(self, ...)
1520 AbstractValue.__init__(self, ...)
1521 self.template = "cbi/fvalue"
1522
1523 self.enabled = "1"
1524 self.disabled = "0"
1525 end
1526
1527 -- A flag can only have two states: set or unset
1528 function Flag.parse(self, section)
1529 local fvalue = self:formvalue(section)
1530
1531 if fvalue then
1532 fvalue = self.enabled
1533 else
1534 fvalue = self.disabled
1535 end
1536
1537 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1538 if not(fvalue == self:cfgvalue(section)) then
1539 self:write(section, fvalue)
1540 end
1541 else
1542 self:remove(section)
1543 end
1544 end
1545
1546
1547
1548 --[[
1549 ListValue - A one-line value predefined in a list
1550 widget: The widget that will be used (select, radio)
1551 ]]--
1552 ListValue = class(AbstractValue)
1553
1554 function ListValue.__init__(self, ...)
1555 AbstractValue.__init__(self, ...)
1556 self.template = "cbi/lvalue"
1557
1558 self.keylist = {}
1559 self.vallist = {}
1560 self.size = 1
1561 self.widget = "select"
1562 end
1563
1564 function ListValue.prepare(self, ...)
1565 AbstractValue.prepare(self, ...)
1566 if not self.override_scheme
1567 and self.map:get_scheme(self.section.sectiontype, self.option) then
1568 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1569 if self.value and vs.valuelist and not self.override_values then
1570 for k, v in ipairs(vs.valuelist) do
1571 local deps = {}
1572 if not self.override_dependencies
1573 and vs.enum_depends and vs.enum_depends[v.value] then
1574 for i, dep in ipairs(vs.enum_depends[v.value]) do
1575 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1576 end
1577 end
1578 self:value(v.value, v.title or v.value, unpack(deps))
1579 end
1580 end
1581 end
1582 end
1583
1584 function ListValue.value(self, key, val, ...)
1585 if luci.util.contains(self.keylist, key) then
1586 return
1587 end
1588
1589 val = val or key
1590 table.insert(self.keylist, tostring(key))
1591 table.insert(self.vallist, tostring(val))
1592
1593 for i, deps in ipairs({...}) do
1594 table.insert(self.deps, {add = "-"..key, deps=deps})
1595 end
1596 end
1597
1598 function ListValue.validate(self, val)
1599 if luci.util.contains(self.keylist, val) then
1600 return val
1601 else
1602 return nil
1603 end
1604 end
1605
1606
1607
1608 --[[
1609 MultiValue - Multiple delimited values
1610 widget: The widget that will be used (select, checkbox)
1611 delimiter: The delimiter that will separate the values (default: " ")
1612 ]]--
1613 MultiValue = class(AbstractValue)
1614
1615 function MultiValue.__init__(self, ...)
1616 AbstractValue.__init__(self, ...)
1617 self.template = "cbi/mvalue"
1618
1619 self.keylist = {}
1620 self.vallist = {}
1621
1622 self.widget = "checkbox"
1623 self.delimiter = " "
1624 end
1625
1626 function MultiValue.render(self, ...)
1627 if self.widget == "select" and not self.size then
1628 self.size = #self.vallist
1629 end
1630
1631 AbstractValue.render(self, ...)
1632 end
1633
1634 function MultiValue.value(self, key, val)
1635 if luci.util.contains(self.keylist, key) then
1636 return
1637 end
1638
1639 val = val or key
1640 table.insert(self.keylist, tostring(key))
1641 table.insert(self.vallist, tostring(val))
1642 end
1643
1644 function MultiValue.valuelist(self, section)
1645 local val = self:cfgvalue(section)
1646
1647 if not(type(val) == "string") then
1648 return {}
1649 end
1650
1651 return luci.util.split(val, self.delimiter)
1652 end
1653
1654 function MultiValue.validate(self, val)
1655 val = (type(val) == "table") and val or {val}
1656
1657 local result
1658
1659 for i, value in ipairs(val) do
1660 if luci.util.contains(self.keylist, value) then
1661 result = result and (result .. self.delimiter .. value) or value
1662 end
1663 end
1664
1665 return result
1666 end
1667
1668
1669 StaticList = class(MultiValue)
1670
1671 function StaticList.__init__(self, ...)
1672 MultiValue.__init__(self, ...)
1673 self.cast = "table"
1674 self.valuelist = self.cfgvalue
1675
1676 if not self.override_scheme
1677 and self.map:get_scheme(self.section.sectiontype, self.option) then
1678 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1679 if self.value and vs.values and not self.override_values then
1680 for k, v in pairs(vs.values) do
1681 self:value(k, v)
1682 end
1683 end
1684 end
1685 end
1686
1687 function StaticList.validate(self, value)
1688 value = (type(value) == "table") and value or {value}
1689
1690 local valid = {}
1691 for i, v in ipairs(value) do
1692 if luci.util.contains(self.keylist, v) then
1693 table.insert(valid, v)
1694 end
1695 end
1696 return valid
1697 end
1698
1699
1700 DynamicList = class(AbstractValue)
1701
1702 function DynamicList.__init__(self, ...)
1703 AbstractValue.__init__(self, ...)
1704 self.template = "cbi/dynlist"
1705 self.cast = "table"
1706 self.keylist = {}
1707 self.vallist = {}
1708 end
1709
1710 function DynamicList.value(self, key, val)
1711 val = val or key
1712 table.insert(self.keylist, tostring(key))
1713 table.insert(self.vallist, tostring(val))
1714 end
1715
1716 function DynamicList.write(self, ...)
1717 self.map.proceed = true
1718 return AbstractValue.write(self, ...)
1719 end
1720
1721 function DynamicList.formvalue(self, section)
1722 local value = AbstractValue.formvalue(self, section)
1723 value = (type(value) == "table") and value or {value}
1724
1725 local valid = {}
1726 for i, v in ipairs(value) do
1727 if v and #v > 0
1728 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1729 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1730 table.insert(valid, v)
1731 end
1732 end
1733
1734 return valid
1735 end
1736
1737
1738 --[[
1739 TextValue - A multi-line value
1740 rows: Rows
1741 ]]--
1742 TextValue = class(AbstractValue)
1743
1744 function TextValue.__init__(self, ...)
1745 AbstractValue.__init__(self, ...)
1746 self.template = "cbi/tvalue"
1747 end
1748
1749 --[[
1750 Button
1751 ]]--
1752 Button = class(AbstractValue)
1753
1754 function Button.__init__(self, ...)
1755 AbstractValue.__init__(self, ...)
1756 self.template = "cbi/button"
1757 self.inputstyle = nil
1758 self.rmempty = true
1759 end
1760
1761
1762 FileUpload = class(AbstractValue)
1763
1764 function FileUpload.__init__(self, ...)
1765 AbstractValue.__init__(self, ...)
1766 self.template = "cbi/upload"
1767 if not self.map.upload_fields then
1768 self.map.upload_fields = { self }
1769 else
1770 self.map.upload_fields[#self.map.upload_fields+1] = self
1771 end
1772 end
1773
1774 function FileUpload.formcreated(self, section)
1775 return AbstractValue.formcreated(self, section) or
1776 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1777 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1778 end
1779
1780 function FileUpload.cfgvalue(self, section)
1781 local val = AbstractValue.cfgvalue(self, section)
1782 if val and fs.access(val) then
1783 return val
1784 end
1785 return nil
1786 end
1787
1788 function FileUpload.formvalue(self, section)
1789 local val = AbstractValue.formvalue(self, section)
1790 if val then
1791 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1792 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1793 then
1794 return val
1795 end
1796 fs.unlink(val)
1797 self.value = nil
1798 end
1799 return nil
1800 end
1801
1802 function FileUpload.remove(self, section)
1803 local val = AbstractValue.formvalue(self, section)
1804 if val and fs.access(val) then fs.unlink(val) end
1805 return AbstractValue.remove(self, section)
1806 end
1807
1808
1809 FileBrowser = class(AbstractValue)
1810
1811 function FileBrowser.__init__(self, ...)
1812 AbstractValue.__init__(self, ...)
1813 self.template = "cbi/browser"
1814 end