radicale.lua 26 KB

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