luci-app-radicale: bump to version 1.1.0
[project/luci.git] / applications / luci-app-radicale / luasrc / model / cbi / radicale.lua
1 -- Copyright 2015-2016 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
2 -- Licensed under the Apache License, Version 2.0
3
4 local NXFS = require("nixio.fs")
5 local DISP = require("luci.dispatcher")
6 local DTYP = require("luci.cbi.datatypes")
7 local HTTP = require("luci.http")
8 local UTIL = require("luci.util")
9 local UCI  = require("luci.model.uci")
10 local SYS  = require("luci.sys")
11 local WADM = require("luci.tools.webadmin")
12 local CTRL = require("luci.controller.radicale")        -- this application's controller and multiused functions
13
14 -- #################################################################################################
15 -- Error handling if not installed or wrong version -- #########################
16 if not CTRL.service_ok() then
17         local f         = SimpleForm("__sf")
18         f.title         = CTRL.app_title_main()
19         f.description   = CTRL.app_description()
20         f.embedded      = true
21         f.submit        = false
22         f.reset         = false
23
24         local s = f:section(SimpleSection)
25         s.title = [[<font color="red">]] .. [[<strong>]]
26                 .. translate("Software update required")
27                 .. [[</strong>]] .. [[</font>]]
28
29         local v   = s:option(DummyValue, "_dv")
30         v.rawhtml = true
31         v.value   = CTRL.app_err_value
32
33         return f
34 end
35
36 -- #################################################################################################
37 -- Error handling if no config, create an empty one -- #########################
38 if not NXFS.access("/etc/config/radicale") then
39         NXFS.writefile("/etc/config/radicale", "")
40 end
41
42 -- #################################################################################################
43 -- takeover arguments if any -- ################################################
44 -- then show/edit selected file
45 if arg[1] then
46         local argument  = arg[1]
47         local filename  = ""
48
49         -- SimpleForm ------------------------------------------------
50         local ft        = SimpleForm("_text")
51         ft.title        = CTRL.app_title_back()
52         ft.description  = CTRL.app_description()
53         ft.redirect     = DISP.build_url("admin", "services", "radicale") .. "#cbi-radicale-" .. argument
54         if argument == "logger" then
55                 ft.reset        = false
56                 ft.submit       = translate("Reload")
57                 local uci       = UCI.cursor()
58                 filename = uci:get("radicale", "logger", "file_path") or "/var/log/radicale"
59                 uci:unload("radicale")
60                 filename = filename .. "/radicale"
61         elseif argument == "auth" then
62                 ft.submit       = translate("Save")
63                 filename = "/etc/radicale/users"
64         elseif argument == "rights" then
65                 ft.submit       = translate("Save")
66                 filename = "/etc/radicale/rights"
67         else
68                 error("Invalid argument given as section")
69         end
70         if argument ~= "logger" and not NXFS.access(filename) then
71                 NXFS.writefile(filename, "")
72         end
73
74         -- SimpleSection ---------------------------------------------
75         local fs        = ft:section(SimpleSection)
76         if argument == "logger" then
77                 fs.title        = translate("Log-file Viewer")
78                 fs.description  = translate("Please press [Reload] button below to reread the file.")
79         elseif argument == "auth" then
80                 fs.title        = translate("Authentication")
81                 fs.description  =  translate("Place here the 'user:password' pairs for your users which should have access to Radicale.")
82                                 .. [[<br /><strong>]]
83                                 .. translate("Keep in mind to use the correct hashing algorithm !")
84                                 .. [[</strong>]]
85         else    -- rights
86                 fs.title        = translate("Rights")
87                 fs.description  =  translate("Authentication login is matched against the 'user' key, "
88                                         .. "and collection's path is matched against the 'collection' key.") .. " "
89                                 .. translate("You can use Python's ConfigParser interpolation values %(login)s and %(path)s.") .. " "
90                                 .. translate("You can also get groups from the user regex in the collection with {0}, {1}, etc.")
91                                 .. [[<br />]]
92                                 .. translate("For example, for the 'user' key, '.+' means 'authenticated user'" .. " "
93                                         .. "and '.*' means 'anybody' (including anonymous users).")
94                                 .. [[<br />]]
95                                 .. translate("Section names are only used for naming the rule.")
96                                 .. [[<br />]]
97                                 .. translate("Leading or ending slashes are trimmed from collection's path.")
98         end
99
100         -- TextValue -------------------------------------------------
101         local tt        = fs:option(TextValue, "_textvalue")
102         tt.rmempty      = true
103         if argument == "logger" then
104                 tt.readonly     = true
105                 tt.rows         = 30
106                 function tt.write()
107                         HTTP.redirect(DISP.build_url("admin", "services", "radicale", "edit", argument))
108                 end
109         else
110                 tt.rows         = 15
111                 function tt.write(self, section, value)
112                         if not value then value = "" end
113                         NXFS.writefile(filename, value:gsub("\r\n", "\n"))
114                         return true --HTTP.redirect(DISP.build_url("admin", "services", "radicale", "edit") .. "#cbi-radicale-" .. argument)
115                 end
116         end
117
118         function tt.cfgvalue()
119                 return NXFS.readfile(filename) or
120                         string.format(translate("File '%s' not found !"), filename)
121         end
122
123         return ft
124
125 end
126
127 -- cbi-map -- ##################################################################
128 local m         = Map("radicale")
129 m.title         = CTRL.app_title_main()
130 m.description   = CTRL.app_description()
131 m.template      = "radicale/tabmap_nsections"
132 m.tabbed        = true
133 function m.commit_handler(self)
134         if self.changed then    -- changes ?
135                 os.execute("/etc/init.d/radicale reload &")     -- reload configuration
136         end
137 end
138
139 -- cbi-section "System" -- #####################################################
140 local sys       = m:section( NamedSection, "system", "system" )
141 sys.title       = translate("System")
142 sys.description = nil
143 function sys.cfgvalue(self, section)
144         if not self.map:get(section) then       -- section might not exist
145                 self.map:set(section, nil, self.sectiontype)
146         end
147         return self.map:get(section)
148 end
149
150 -- start/stop button -----------------------------------------------------------
151 local btn       = sys:option(DummyValue, "_startstop")
152 btn.template    = "radicale/btn_startstop"
153 btn.inputstyle  = nil
154 btn.rmempty     = true
155 btn.title       = translate("Start / Stop")
156 btn.description = translate("Start/Stop Radicale server")
157 function btn.cfgvalue(self, section)
158         local pid = CTRL.get_pid(true)
159         if pid > 0 then
160                 btn.inputtitle  = "PID: " .. pid
161                 btn.inputstyle  = "reset"
162                 btn.disabled    = false
163         else
164                 btn.inputtitle  = translate("Start")
165                 btn.inputstyle  = "apply"
166                 btn.disabled    = false
167         end
168         return true
169 end
170
171 -- enabled ---------------------------------------------------------------------
172 local ena       = sys:option(Flag, "_enabled")
173 ena.title       = translate("Auto-start")
174 ena.description = translate("Enable/Disable auto-start of Radicale on system start-up and interface events")
175 ena.orientation = "horizontal"  -- put description under the checkbox
176 ena.rmempty     = false         -- force write() function
177 function ena.cfgvalue(self, section)
178         return (SYS.init.enabled("radicale")) and self.enabled or self.disabled
179 end
180 function ena.write(self, section, value)
181         if value == self.enabled then
182                 return SYS.init.enable("radicale")
183         else
184                 return SYS.init.disable("radicale")
185         end
186 end
187
188 -- boot_delay ------------------------------------------------------------------
189 local bd        = sys:option(Value, "boot_delay")
190 bd.title        = translate("Boot delay")
191 bd.description  = translate("Delay (in seconds) during system boot before Radicale start")
192                 .. [[<br />]]
193                 .. translate("During delay ifup-events are not monitored !")
194 bd.default      = "10"
195 function bd.parse(self, section, novld)
196         CTRL.value_parse(self, section, novld)
197 end
198 function bd.validate(self, value)
199         local val = tonumber(value)
200         if not val then
201                 return nil, self.title .. ": " .. translate("Value is not a number")
202         elseif val < 0 or val > 300 then
203                 return nil, self.title .. ": " .. translate("Value not between 0 and 300")
204         end
205         return value
206 end
207
208
209 -- cbi-section "Server" -- #####################################################
210 local srv       = m:section( NamedSection, "server", "setting" )
211 srv.title       = translate("Server")
212 srv.description = nil
213 function srv.cfgvalue(self, section)
214         if not self.map:get(section) then       -- section might not exist
215                 self.map:set(section, nil, self.sectiontype)
216         end
217         return self.map:get(section)
218 end
219
220 -- hosts -----------------------------------------------------------------------
221 local sh        = srv:option( DynamicList, "hosts" )
222 sh.title        = translate("Address:Port")
223 sh.description  = translate("'Hostname:Port' or 'IPv4:Port' or '[IPv6]:Port' Radicale should listen on")
224                 .. [[<br /><strong>]]
225                 .. translate("Port numbers below 1024 (Privileged ports) are not supported")
226                 .. [[</strong>]]
227 sh.placeholder  = "0.0.0.0:5232"
228 sh.rmempty      = true
229 function sh.parse(self, section, novld)
230         CTRL.value_parse(self, section, novld)
231 end
232
233 -- realm -----------------------------------------------------------------------
234 local alm       = srv:option( Value, "realm" )
235 alm.title       = translate("Logon message")
236 alm.description = translate("Message displayed in the client when a password is needed.")
237 alm.default     = "Radicale - Password Required"
238 function alm.parse(self, section, novld)
239         CTRL.value_parse(self, section, novld)
240 end
241 function alm.validate(self, value)
242         if value then
243                 return value
244         else
245                 return self.default
246         end
247 end
248
249 -- ssl -------------------------------------------------------------------------
250 local ssl       = srv:option( Flag, "ssl" )
251 ssl.title       = translate("Enable HTTPS")
252 ssl.description = nil
253 function ssl.write(self, section, value)
254         if value == "0" then                                    -- delete all if not https enabled
255                 self.map:del(section, "protocol")               -- protocol
256                 self.map:del(section, "certificate")            -- certificate
257                 self.map:del(section, "key")                    -- private key
258                 self.map:del(section, "ciphers")                -- ciphers
259                 return self.map:del(section, self.option)
260         else
261                 return self.map:set(section, self.option, value)
262         end
263 end
264
265 -- protocol --------------------------------------------------------------------
266 local prt       = srv:option( ListValue, "protocol" )
267 prt.title       = translate("SSL Protocol")
268 prt.description = translate("'AUTO' selects the highest protocol version that client and server support.")
269 prt.widget      = "select"
270 prt.default     = "PROTOCOL_SSLv23"
271 prt:depends     ("ssl", "1")
272 prt:value       ("PROTOCOL_SSLv23", translate("AUTO"))
273 prt:value       ("PROTOCOL_SSLv2", "SSL v2")
274 prt:value       ("PROTOCOL_SSLv3", "SSL v3")
275 prt:value       ("PROTOCOL_TLSv1", "TLS v1")
276 prt:value       ("PROTOCOL_TLSv1_1", "TLS v1.1")
277 prt:value       ("PROTOCOL_TLSv1_2", "TLS v1.2")
278 function prt.parse(self, section, novld)
279         CTRL.value_parse(self, section, novld)
280 end
281
282 -- certificate -----------------------------------------------------------------
283 local crt       = srv:option( Value, "certificate" )
284 crt.title       = translate("Certificate file")
285 crt.description = translate("Full path and file name of certificate")
286 crt.placeholder = "/etc/radicale/ssl/server.crt"
287 crt:depends     ("ssl", "1")
288 function crt.parse(self, section, novld)
289         CTRL.value_parse(self, section, novld)
290 end
291 function crt.validate(self, value)
292         local _ssl = ssl:formvalue(srv.section) or "0"
293         if _ssl == "0" then
294                 return ""       -- ignore if not https enabled
295         end
296         if value then           -- otherwise errors in datatype check
297                 if DTYP.file(value) then
298                         return value
299                 else
300                         return nil, self.title .. ": " .. translate("File not found !")
301                 end
302         else
303                 return nil, self.title .. ": " .. translate("Path/File required !")
304         end
305 end
306
307 -- key -------------------------------------------------------------------------
308 local key       = srv:option( Value, "key" )
309 key.title       = translate("Private key file")
310 key.description = translate("Full path and file name of private key")
311 key.placeholder = "/etc/radicale/ssl/server.key"
312 key:depends     ("ssl", "1")
313 function key.parse(self, section, novld)
314         CTRL.value_parse(self, section, novld)
315 end
316 function key.validate(self, value)
317         local _ssl = ssl:formvalue(srv.section) or "0"
318         if _ssl == "0" then
319                 return ""       -- ignore if not https enabled
320         end
321         if value then           -- otherwise errors in datatype check
322                 if DTYP.file(value) then
323                         return value
324                 else
325                         return nil, self.title .. ": " .. translate("File not found !")
326                 end
327         else
328                 return nil, self.title .. ": " .. translate("Path/File required !")
329         end
330 end
331
332 -- ciphers ---------------------------------------------------------------------
333 --local cip     = srv:option( Value, "ciphers" )
334 --cip.title     = translate("Ciphers")
335 --cip.description       = translate("OPTIONAL: See python's ssl module for available ciphers")
336 --cip.rmempty   = true
337 --cip:depends   ("ssl", "1")
338
339 -- cbi-section "Authentication" -- #############################################
340 local aut       = m:section( NamedSection, "auth", "setting" )
341 aut.title       = translate("Authentication")
342 aut.description = translate("Authentication method to allow access to Radicale server.")
343 function aut.cfgvalue(self, section)
344         if not self.map:get(section) then       -- section might not exist
345                 self.map:set(section, nil, self.sectiontype)
346         end
347         return self.map:get(section)
348 end
349
350 -- type -----------------------------------------------------------------------
351 local aty       = aut:option( ListValue, "type" )
352 aty.title       = translate("Authentication method")
353 aty.description = nil
354 aty.widget      = "select"
355 aty.default     = "None"
356 aty:value       ("None", translate("None"))
357 aty:value       ("htpasswd", translate("htpasswd file"))
358 --aty:value     ("IMAP", "IMAP")                        -- The IMAP authentication module relies on the imaplib module.
359 --aty:value     ("LDAP", "LDAP")                        -- The LDAP authentication module relies on the python-ldap module.
360 --aty:value     ("PAM", "PAM")                          -- The PAM authentication module relies on the python-pam module.
361 --aty:value     ("courier", "courier")
362 --aty:value     ("HTTP", "HTTP")                        -- The HTTP authentication module relies on the requests module
363 --aty:value     ("remote_user", "remote_user")
364 --aty:value     ("custom", translate("custom"))
365 function aty.parse(self, section, novld)
366         CTRL.value_parse(self, section, novld)
367 end
368 function aty.write(self, section, value)
369         if value ~= "htpasswd" then
370                 self.map:del(section, "htpasswd_encryption")
371         elseif value ~= "IMAP" then
372                 self.map:del(section, "imap_hostname")
373                 self.map:del(section, "imap_port")
374                 self.map:del(section, "imap_ssl")
375         end
376         if value ~= self.default then
377                 return self.map:set(section, self.option, value)
378         else
379                 return self.map:del(section, self.option)
380         end
381 end
382
383 -- htpasswd_encryption ---------------------------------------------------------
384 local hte       = aut:option( ListValue, "htpasswd_encryption" )
385 hte.title       = translate("Encryption method")
386 hte.description = nil
387 hte.widget      = "select"
388 hte.default     = "crypt"
389 hte:depends     ("type", "htpasswd")
390 hte:value       ("crypt", translate("crypt"))
391 hte:value       ("plain", translate("plain"))
392 hte:value       ("sha1",  translate("SHA-1"))
393 hte:value       ("ssha",  translate("salted SHA-1"))
394 function hte.parse(self, section, novld)
395         CTRL.value_parse(self, section, novld)
396 end
397
398 -- htpasswd_file (dummy) -------------------------------------------------------
399 local htf       = aut:option( Value, "_htf" )
400 htf.title       = translate("htpasswd file")
401 htf.description = [[<strong>]]
402                 .. translate("Read only!")
403                 .. [[</strong> ]]
404                 .. translate("Radicale uses '/etc/radicale/users' as htpasswd file.")
405                 .. [[<br /><a href="]]
406                 .. DISP.build_url("admin", "services", "radicale", "edit") .. [[/auth]]
407                 .. [[">]]
408                 .. translate("To edit the file follow this link!")
409                 .. [[</a>]]
410 htf.readonly    = true
411 htf:depends     ("type", "htpasswd")
412 function htf.cfgvalue()
413         return "/etc/radicale/users"
414 end
415
416 -- cbi-section "Rights" -- #####################################################
417 local rig       = m:section( NamedSection, "rights", "setting" )
418 rig.title       = translate("Rights")
419 rig.description = translate("Control the access to data collections.")
420 function rig.cfgvalue(self, section)
421         if not self.map:get(section) then       -- section might not exist
422                 self.map:set(section, nil, self.sectiontype)
423         end
424         return self.map:get(section)
425 end
426
427 -- type -----------------------------------------------------------------------
428 local rty       = rig:option( ListValue, "type" )
429 rty.title       = translate("Rights backend")
430 rty.description = nil
431 rty.widget      = "select"
432 rty.default     = "None"
433 rty:value       ("None", translate("Full access for everybody (including anonymous)"))
434 rty:value       ("authenticated", translate("Full access for authenticated Users") )
435 rty:value       ("owner_only", translate("Full access for Owner only") )
436 rty:value       ("owner_write", translate("Owner allow write, authenticated users allow read") )
437 rty:value       ("from_file", translate("Rights are based on a regexp-based file") )
438 --rty:value     ("custom", "Custom handler")
439 function rty.parse(self, section, novld)
440         CTRL.value_parse(self, section, novld)
441 end
442 function rty.write(self, section, value)
443         if value ~= "custom" then
444                 self.map:del(section, "custom_handler")
445         end
446         if value ~= self.default then
447                 return self.map:set(section, self.option, value)
448         else
449                 return self.map:del(section, self.option)
450         end
451 end
452
453 -- from_file (dummy) -----------------------------------------------------------
454 local rtf       = rig:option( Value, "_rtf" )
455 rtf.title       = translate("RegExp file")
456 rtf.description = [[<strong>]]
457                 .. translate("Read only!")
458                 .. [[</strong> ]]
459                 .. translate("Radicale uses '/etc/radicale/rights' as regexp-based file.")
460                 .. [[<br /><a href="]]
461                 .. DISP.build_url("admin", "services", "radicale", "edit") .. [[/rights]]
462                 .. [[">]]
463                 .. translate("To edit the file follow this link!")
464                 .. [[</a>]]
465 rtf.readonly    = true
466 rtf:depends     ("type", "from_file")
467 function rtf.cfgvalue()
468         return "/etc/radicale/rights"
469 end
470
471 -- cbi-section "Storage" -- ####################################################
472 local sto       = m:section( NamedSection, "storage", "setting" )
473 sto.title       = translate("Storage")
474 sto.description = nil
475 function sto.cfgvalue(self, section)
476         if not self.map:get(section) then       -- section might not exist
477                 self.map:set(section, nil, self.sectiontype)
478         end
479         return self.map:get(section)
480 end
481
482 -- type -----------------------------------------------------------------------
483 local sty       = sto:option( ListValue, "type" )
484 sty.title       = translate("Storage backend")
485 sty.description = translate("WARNING: Only 'File-system' is documented and tested by Radicale development")
486 sty.widget      = "select"
487 sty.default     = "filesystem"
488 sty:value       ("filesystem", translate("File-system"))
489 --sty:value     ("multifilesystem", translate("") )
490 --sty:value     ("database", translate("Database") )
491 --sty:value     ("custom", translate("Custom") )
492 function sty.parse(self, section, novld)
493         CTRL.value_parse(self, section, novld)
494 end
495 function sty.write(self, section, value)
496         if value ~= "filesystem" then
497                 self.map:del(section, "filesystem_folder")
498         end
499         if value ~= self.default then
500                 return self.map:set(section, self.option, value)
501         else
502                 return self.map:del(section, self.option)
503         end
504 end
505
506 --filesystem_folder ------------------------------------------------------------
507 local sfi       = sto:option( Value, "filesystem_folder" )
508 sfi.title       = translate("Directory")
509 sfi.description = nil
510 sfi.placeholder = "/srv/radicale"
511 sfi:depends     ("type", "filesystem")
512 function sfi.parse(self, section, novld)
513         CTRL.value_parse(self, section, novld)
514 end
515 function sfi.validate(self, value)
516         local _typ = sty:formvalue(sto.section) or ""
517         if _typ ~= "filesystem" then
518                 return ""       -- ignore if not htpasswd
519         end
520         if value then           -- otherwise errors in datatype check
521                 if DTYP.directory(value) then
522                         return value
523                 else
524                         return nil, self.title .. ": " .. translate("Directory not exists/found !")
525                 end
526         else
527                 return nil, self.title .. ": " .. translate("Directory required !")
528         end
529 end
530
531 -- cbi-section "Logging" -- ####################################################
532 local log       = m:section( NamedSection, "logger", "logging" )
533 log.title       = translate("Logging")
534 log.description = nil
535 function log.cfgvalue(self, section)
536         if not self.map:get(section) then       -- section might not exist
537                 self.map:set(section, nil, self.sectiontype)
538         end
539         return self.map:get(section)
540 end
541
542 -- console_level ---------------------------------------------------------------
543 local lco       = log:option( ListValue, "console_level" )
544 lco.title       = translate("Console Log level")
545 lco.description = nil
546 lco.widget      = "select"
547 lco.default     = "ERROR"
548 lco:value       ("DEBUG", translate("Debug"))
549 lco:value       ("INFO", translate("Info") )
550 lco:value       ("WARNING", translate("Warning") )
551 lco:value       ("ERROR", translate("Error") )
552 lco:value       ("CRITICAL", translate("Critical") )
553 function lco.parse(self, section, novld)
554         CTRL.value_parse(self, section, novld)
555 end
556 function lco.write(self, section, value)
557         if value ~= self.default then
558                 return self.map:set(section, self.option, value)
559         else
560                 return self.map:del(section, self.option)
561         end
562 end
563
564 -- syslog_level ----------------------------------------------------------------
565 local lsl       = log:option( ListValue, "syslog_level" )
566 lsl.title       = translate("Syslog Log level")
567 lsl.description = nil
568 lsl.widget      = "select"
569 lsl.default     = "WARNING"
570 lsl:value       ("DEBUG", translate("Debug"))
571 lsl:value       ("INFO", translate("Info") )
572 lsl:value       ("WARNING", translate("Warning") )
573 lsl:value       ("ERROR", translate("Error") )
574 lsl:value       ("CRITICAL", translate("Critical") )
575 function lsl.parse(self, section, novld)
576         CTRL.value_parse(self, section, novld)
577 end
578 function lsl.write(self, section, value)
579         if value ~= self.default then
580                 return self.map:set(section, self.option, value)
581         else
582                 return self.map:del(section, self.option)
583         end
584 end
585
586 -- file_level ------------------------------------------------------------------
587 local lfi       = log:option( ListValue, "file_level" )
588 lfi.title       = translate("File Log level")
589 lfi.description = nil
590 lfi.widget      = "select"
591 lfi.default     = "INFO"
592 lfi:value       ("DEBUG", translate("Debug"))
593 lfi:value       ("INFO", translate("Info") )
594 lfi:value       ("WARNING", translate("Warning") )
595 lfi:value       ("ERROR", translate("Error") )
596 lfi:value       ("CRITICAL", translate("Critical") )
597 function lfi.parse(self, section, novld)
598         CTRL.value_parse(self, section, novld)
599 end
600 function lfi.write(self, section, value)
601         if value ~= self.default then
602                 return self.map:set(section, self.option, value)
603         else
604                 return self.map:del(section, self.option)
605         end
606 end
607
608 -- file_path -------------------------------------------------------------------
609 local lfp       = log:option( Value, "file_path" )
610 lfp.title       = translate("Log-file directory")
611 lfp.description = translate("Directory where the rotating log-files are stored")
612                 .. [[<br /><a href="]]
613                 .. DISP.build_url("admin", "services", "radicale", "edit") .. [[/logger]]
614                 .. [[">]]
615                 .. translate("To view latest log file follow this link!")
616                 .. [[</a>]]
617 lfp.default     = "/var/log/radicale"
618 function lfp.parse(self, section, novld)
619         CTRL.value_parse(self, section, novld)
620 end
621 function lfp.validate(self, value)
622         if not value or (#value < 1) or (value:find("/") ~= 1) then
623                 return nil, self.title .. ": " .. translate("no valid path given!")
624         end
625         return value
626 end
627
628 -- file_maxbytes ---------------------------------------------------------------
629 local lmb       = log:option( Value, "file_maxbytes" )
630 lmb.title       = translate("Log-file size")
631 lmb.description = translate("Maximum size of each rotation log-file.")
632                 .. [[<br /><strong>]]
633                 .. translate("Setting this parameter to '0' will disable rotation of log-file.")
634                 .. [[</strong>]]
635 lmb.default     = "8196"
636 function lmb.parse(self, section, novld)
637         CTRL.value_parse(self, section, novld)
638 end
639 function lmb.validate(self, value)
640         if value then           -- otherwise errors in datatype check
641                 if DTYP.uinteger(value) then
642                         return value
643                 else
644                         return nil, self.title .. ": " .. translate("Value is not an Integer >= 0 !")
645                 end
646         else
647                 return nil, self.title .. ": " .. translate("Value required ! Integer >= 0 !")
648         end
649 end
650
651 -- file_backupcount ------------------------------------------------------------
652 local lbc       = log:option( Value, "file_backupcount" )
653 lbc.title       = translate("Log-backup Count")
654 lbc.description = translate("Number of backup files of log to create.")
655                 .. [[<br /><strong>]]
656                 .. translate("Setting this parameter to '0' will disable rotation of log-file.")
657                 .. [[</strong>]]
658 lbc.default     = "1"
659 function lbc.parse(self, section, novld)
660         CTRL.value_parse(self, section, novld)
661 end
662 function lbc.validate(self, value)
663         if value then           -- otherwise errors in datatype check
664                 if DTYP.uinteger(value) then
665                         return value
666                 else
667                         return nil, self.title .. ": " .. translate("Value is not an Integer >= 0 !")
668                 end
669         else
670                 return nil, self.title .. ": " .. translate("Value required ! Integer >= 0 !")
671         end
672 end
673
674 -- cbi-section "Encoding" -- ###################################################
675 local enc       = m:section( NamedSection, "encoding", "setting" )
676 enc.title       = translate("Encoding")
677 enc.description = translate("Change here the encoding Radicale will use instead of 'UTF-8' "
678                 .. "for responses to the client and/or to store data inside collections.")
679 function enc.cfgvalue(self, section)
680         if not self.map:get(section) then       -- section might not exist
681                 self.map:set(section, nil, self.sectiontype)
682         end
683         return self.map:get(section)
684 end
685
686 -- request ---------------------------------------------------------------------
687 local enr       = enc:option( Value, "request" )
688 enr.title       = translate("Response Encoding")
689 enr.description = translate("Encoding for responding requests.")
690 enr.default     = "utf-8"
691 function enr.parse(self, section, novld)
692         CTRL.value_parse(self, section, novld)
693 end
694
695 -- stock -----------------------------------------------------------------------
696 local ens       = enc:option( Value, "stock" )
697 ens.title       = translate("Storage Encoding")
698 ens.description = translate("Encoding for storing local collections.")
699 ens.default     = "utf-8"
700 function ens.parse(self, section, novld)
701         CTRL.value_parse(self, section, novld)
702 end
703
704 -- cbi-section "Headers" -- ####################################################
705 local hea       = m:section( NamedSection, "headers", "setting" )
706 hea.title       = translate("Additional HTTP headers")
707 hea.description = translate("Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources (e.g. fonts, JavaScript, etc.) "
708                 .. "on a web page to be requested from another domain outside the domain from which the resource originated.")
709 function hea.cfgvalue(self, section)
710         if not self.map:get(section) then       -- section might not exist
711                 self.map:set(section, nil, self.sectiontype)
712         end
713         return self.map:get(section)
714 end
715
716 -- Access_Control_Allow_Origin -------------------------------------------------
717 local heo       = hea:option( DynamicList, "Access_Control_Allow_Origin" )
718 heo.title       = translate("Access-Control-Allow-Origin")
719 heo.description = nil
720 function heo.parse(self, section, novld)
721         CTRL.value_parse(self, section, novld)
722 end
723
724 -- Access_Control_Allow_Methods ------------------------------------------------
725 local hem       = hea:option( DynamicList, "Access_Control_Allow_Methods" )
726 hem.title       = translate("Access-Control-Allow-Methods")
727 hem.description = nil
728 function hem.parse(self, section, novld)
729         CTRL.value_parse(self, section, novld)
730 end
731
732 -- Access_Control_Allow_Headers ------------------------------------------------
733 local heh       = hea:option( DynamicList, "Access_Control_Allow_Headers" )
734 heh.title       = translate("Access-Control-Allow-Headers")
735 heh.description = nil
736 function heh.parse(self, section, novld)
737         CTRL.value_parse(self, section, novld)
738 end
739
740 -- Access_Control_Expose_Headers -----------------------------------------------
741 local hee       = hea:option( DynamicList, "Access_Control_Expose_Headers" )
742 hee.title       = translate("Access-Control-Expose-Headers")
743 hee.description = nil
744 function hee.parse(self, section, novld)
745         CTRL.value_parse(self, section, novld)
746 end
747
748 return m