86658d70209bf0fbc79c108b5791519cbc4dec7d
[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 origin, name = next(self.map:formvaluetable(crval))
1099                 if self.anonymous then
1100                         if name then
1101                                 created = self:create(nil, origin)
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, origin) 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