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