radicale.lua 27 KB

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