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