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