diff options
author | Christian Schoenebeck <christian.schoenebeck@gmail.com> | 2015-05-08 20:44:13 +0200 |
---|---|---|
committer | Christian Schoenebeck <christian.schoenebeck@gmail.com> | 2015-05-08 20:44:13 +0200 |
commit | cc99288242be3e0fe60b61b83bebe272222560c5 (patch) | |
tree | 306cd8d6d12c6c8b7823b0ca52906258a61bb62b /applications | |
parent | a7083780de7c9a52feea80b2d6cee6781e3193f3 (diff) | |
parent | e6804f0a9313d5aa207994a4e3a365d5373c8abf (diff) |
Merge pull request #376 from chris5560/master
luci-app-radicale: New app to support Radicale CalDAV/CardDAV server
Diffstat (limited to 'applications')
8 files changed, 1911 insertions, 0 deletions
diff --git a/applications/luci-app-radicale/Makefile b/applications/luci-app-radicale/Makefile new file mode 100644 index 0000000000..07998aee58 --- /dev/null +++ b/applications/luci-app-radicale/Makefile @@ -0,0 +1,41 @@ +# +# Copyright (C) 2008-2015 The LuCI Team <luci@lists.subsignal.org> +# +# This is free software, licensed under the Apache License, Version 2.0 . +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-radicale + +# Version == major.minor.patch +# increase "minor" on new functionality and "patch" on patches/optimization +PKG_VERSION:=1.0.0 + +# Release == build +# increase on changes of translation files +PKG_RELEASE:=1 + +PKG_LICENSE:=Apache-2.0 +PKG_MAINTAINER:=Christian Schoenebeck <christian.schoenebeck@gmail.com> + +# LuCI specific settings +LUCI_TITLE:=LuCI Support for Radicale CardDAV/CalDAV +LUCI_DEPENDS:=+luci-mod-admin-full +LUCI_PKGARCH:=all + +define Package/$(PKG_NAME)/config +# shown in make menuconfig <Help> +help + $(LUCI_TITLE) + . + !!! Package "radicale-py2" or "radicale-py3" needs to be !!! + !!! installed sepearatly. There is no buildin dependency set !!! + . + Version: $(PKG_VERSION)-$(PKG_RELEASE) + $(PKG_MAINTAINER) +endef + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/applications/luci-app-radicale/luasrc/controller/radicale.lua b/applications/luci-app-radicale/luasrc/controller/radicale.lua new file mode 100755 index 0000000000..662c60d5a6 --- /dev/null +++ b/applications/luci-app-radicale/luasrc/controller/radicale.lua @@ -0,0 +1,210 @@ +-- Copyright 2014 Christian Schoenebeck <christian dot schoenebeck at gmail dot com> +-- Licensed under the Apache License, Version 2.0 + +module("luci.controller.radicale", package.seeall) + +local NX = require("nixio") +local NXFS = require("nixio.fs") +local DISP = require "luci.dispatcher" +local HTTP = require("luci.http") +local I18N = require("luci.i18n") -- not globally avalible here +local UTIL = require("luci.util") +local SYS = require("luci.sys") + +function index() + entry( {"admin", "services", "radicale"}, alias("admin", "services", "radicale", "edit"), _("CalDAV/CardDAV"), 58) + entry( {"admin", "services", "radicale", "edit"}, cbi("radicale") ).leaf = true + entry( {"admin", "services", "radicale", "logview"}, call("_logread") ).leaf = true + entry( {"admin", "services", "radicale", "startstop"}, call("_startstop") ).leaf = true + entry( {"admin", "services", "radicale", "status"}, call("_status") ).leaf = true +end + +-- called by XHR.get from detail_logview.htm +function _logread() + -- read application settings + local uci = UCI.cursor() + local logfile = uci:get("radicale", "radicale", "logfile") or "/var/log/radicale" + uci:unload("radicale") + + local ldata=NXFS.readfile(logfile) + if not ldata or #ldata == 0 then + ldata="_nodata_" + end + HTTP.write(ldata) +end + +-- called by XHR.get from detail_startstop.htm +function _startstop() + local pid = get_pid() + if pid > 0 then + SYS.call("/etc/init.d/radicale stop") + NX.nanosleep(1) -- sleep a second + if NX.kill(pid, 0) then -- still running + NX.kill(pid, 9) -- send SIGKILL + end + pid = 0 + else + SYS.call("/etc/init.d/radicale start") + NX.nanosleep(1) -- sleep a second + pid = get_pid() + if pid > 0 and not NX.kill(pid, 0) then + pid = 0 -- process did not start + end + end + HTTP.write(tostring(pid)) -- HTTP needs string not number +end + +-- called by XHR.poll from detail_startstop.htm +function _status() + local pid = get_pid() + HTTP.write(tostring(pid)) -- HTTP needs string not number +end + +-- Application / Service specific information functions ######################## +function luci_app_name() + return "luci-app-radicale" +end + +function service_name() + return "radicale" +end +function service_required() + return "0.10-1" +end +function service_installed() + local v = ipkg_ver_installed("radicale-py2") + if not v or #v == 0 then v = ipkg_ver_installed("radicale-py3") end + if not v or #v == 0 then v = "0" end + return v +end +function service_ok() + return ipkg_ver_compare(service_installed(),">=",service_required()) +end + +function app_title_main() + return [[</a><a href="javascript:alert(']] + .. I18N.translate("Version Information") + .. [[\n\n]] .. luci_app_name() + .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]] + .. (ipkg_ver_installed(luci_app_name()) == "" + and I18N.translate("NOT installed") + or ipkg_ver_installed(luci_app_name()) ) + .. [[\n\n]] .. service_name() .. [[ ]] .. I18N.translate("required") .. [[:]] + .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]] + .. service_required() .. [[ ]] .. I18N.translate("or higher") + .. [[\n\n]] .. service_name() .. [[ ]] .. I18N.translate("installed") .. [[:]] + .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]] + .. (service_installed() == "0" + and I18N.translate("NOT installed") + or service_installed()) + .. [[\n\n]] + .. [[')">]] + .. I18N.translate("Radicale CalDAV/CardDAV Server") +end +function app_title_back() + return [[</a><a href="]] + .. DISP.build_url("admin", "services", "radicale") + .. [[">]] + .. I18N.translate("Radicale CalDAV/CardDAV Server") +end +function app_description() + return I18N.translate("The Radicale Project is a complete CalDAV (calendar) and CardDAV (contact) server solution.") .. [[<br />]] + .. I18N.translate("Calendars and address books are available for both local and remote access, possibly limited through authentication policies.") .. [[<br />]] + .. I18N.translate("They can be viewed and edited by calendar and contact clients on mobile phones or computers.") +end + +-- other multiused functions ################################################### + +--return pid of running process +function get_pid() + return tonumber(SYS.exec([[ps | grep "[p]ython.*[r]adicale" 2>/dev/null | awk '{print $1}']])) or 0 +end + +-- compare versions using "<=" "<" ">" ">=" "=" "<<" ">>" +function ipkg_ver_compare(ver1, comp, ver2) + if not ver1 or not (#ver1 > 0) + or not ver2 or not (#ver2 > 0) + or not comp or not (#comp > 0) then return nil end + -- correct compare string + if comp == "<>" or comp == "><" or comp == "!=" or comp == "~=" then comp = "~=" + elseif comp == "<=" or comp == "<" or comp == "=<" then comp = "<=" + elseif comp == ">=" or comp == ">" or comp == "=>" then comp = ">=" + elseif comp == "=" or comp == "==" then comp = "==" + elseif comp == "<<" then comp = "<" + elseif comp == ">>" then comp = ">" + else return nil end + + local av1 = UTIL.split(ver1, "[%.%-]", nil, true) + local av2 = UTIL.split(ver2, "[%.%-]", nil, true) + + for i = 1, math.max(table.getn(av1),table.getn(av2)), 1 do + local s1 = av1[i] or "" + local s2 = av2[i] or "" + local n1 = tonumber(s1) + local n2 = tonumber(s2) + + -- one numeric and other empty string then set other to 0 + if n1 and not n2 and (not s2 or #s2 == 0) then n2 = 0 end + if n2 and not n1 and (not s1 or #s1 == 0) then n1 = 0 end + + local nc = (n1 and n2) -- numeric compare + + if nc then + -- first "not equal" found return true + if comp == "~=" and (n1 ~= n2) then return true end + -- first "lower" found return true + if (comp == "<" or comp == "<=") and (n1 < n2) then return true end + -- first "greater" found return true + if (comp == ">" or comp == ">=") and (n1 > n2) then return true end + -- not equal then return false + if (n1 ~= n2) then return false end + else + if comp == "~=" and (s1 ~= s2) then return true end + if (comp == "<" or comp == "<=") and (s1 < s2) then return true end + if (comp == ">" or comp == ">=") and (s1 > s2) then return true end + if (s1 ~= s2) then return false end + end + end + -- all equal then true + return true +end + +-- read version information for given package if installed +function ipkg_ver_installed(pkg) + local version = "" + local control = io.open("/usr/lib/opkg/info/%s.control" % pkg, "r") + if control then + local ln + repeat + ln = control:read("*l") + if ln and ln:match("^Version: ") then + version = ln:gsub("^Version: ", "") + break + end + until not ln + control:close() + end + return version +end + +-- replacement of build-in Flag.parse of cbi.lua +-- modified to mark section as changed if value changes +-- current parse did not do this, but it is done AbstaractValue.parse() +function flag_parse(self, section) + local fexists = self.map:formvalue( + luci.cbi.FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option) + + if fexists then + local fvalue = self:formvalue(section) and self.enabled or self.disabled + local cvalue = self:cfgvalue(section) + if fvalue ~= self.default or (not self.optional and not self.rmempty) then + self:write(section, fvalue) + else + self:remove(section) + end + if (fvalue ~= cvalue) then self.section.changed = true end + else + self:remove(section) + self.section.changed = true + end +end diff --git a/applications/luci-app-radicale/luasrc/model/cbi/radicale.lua b/applications/luci-app-radicale/luasrc/model/cbi/radicale.lua new file mode 100755 index 0000000000..8abb68869d --- /dev/null +++ b/applications/luci-app-radicale/luasrc/model/cbi/radicale.lua @@ -0,0 +1,748 @@ +-- Copyright 2015 Christian Schoenebeck <christian dot schoenebeck at gmail dot com> +-- Licensed under the Apache License, Version 2.0 + +local NXFS = require("nixio.fs") +local DISP = require("luci.dispatcher") +local DTYP = require("luci.cbi.datatypes") +local HTTP = require("luci.http") +local UTIL = require("luci.util") +local UCI = require("luci.model.uci") +local SYS = require("luci.sys") +local TOOLS = require("luci.controller.radicale") -- this application's controller and multiused functions + +-- ################################################################################################# +-- takeover arguments if any -- ################################################ +-- then show/edit selected file +if arg[1] then + local argument = arg[1] + local filename = "" + + -- SimpleForm ------------------------------------------------ + local ft = SimpleForm("_text") + ft.title = TOOLS.app_title_back() + ft.description = TOOLS.app_description() + ft.redirect = DISP.build_url("admin", "services", "radicale") .. "#cbi-radicale-" .. argument + if argument == "logger" then + ft.reset = false + ft.submit = translate("Reload") + local uci = UCI.cursor() + filename = uci:get("radicale", "logger", "file_path") or "/var/log/radicale" + uci:unload("radicale") + filename = filename .. "/radicale" + elseif argument == "auth" then + ft.submit = translate("Save") + filename = "/etc/radicale/users" + elseif argument == "rights" then + ft.submit = translate("Save") + filename = "/etc/radicale/rights" + else + error("Invalid argument given as section") + end + if argument ~= "logger" and not NXFS.access(filename) then + NXFS.writefile(filename, "") + end + + -- SimpleSection --------------------------------------------- + local fs = ft:section(SimpleSection) + if argument == "logger" then + fs.title = translate("Log-file Viewer") + fs.description = translate("Please press [Reload] button below to reread the file.") + elseif argument == "auth" then + fs.title = translate("Authentication") + fs.description = translate("Place here the 'user:password' pairs for your users which should have access to Radicale.") + .. [[<br /><strong>]] + .. translate("Keep in mind to use the correct hashing algorithm !") + .. [[</strong>]] + else -- rights + fs.title = translate("Rights") + fs.description = translate("Authentication login is matched against the 'user' key, " + .. "and collection's path is matched against the 'collection' key.") .. " " + .. translate("You can use Python's ConfigParser interpolation values %(login)s and %(path)s.") .. " " + .. translate("You can also get groups from the user regex in the collection with {0}, {1}, etc.") + .. [[<br />]] + .. translate("For example, for the 'user' key, '.+' means 'authenticated user'" .. " " + .. "and '.*' means 'anybody' (including anonymous users).") + .. [[<br />]] + .. translate("Section names are only used for naming the rule.") + .. [[<br />]] + .. translate("Leading or ending slashes are trimmed from collection's path.") + end + + -- TextValue ------------------------------------------------- + local tt = fs:option(TextValue, "_textvalue") + tt.rmempty = true + if argument == "logger" then + tt.readonly = true + tt.rows = 30 + function tt.write() + HTTP.redirect(DISP.build_url("admin", "services", "radicale", "edit", argument)) + end + else + tt.rows = 15 + function tt.write(self, section, value) + if not value then value = "" end + NXFS.writefile(filename, value:gsub("\r\n", "\n")) + return true --HTTP.redirect(DISP.build_url("admin", "services", "radicale", "edit") .. "#cbi-radicale-" .. argument) + end + end + + function tt.cfgvalue() + return NXFS.readfile(filename) or + string.format(translate("File '%s' not found !"), filename) + end + + return ft + +end + +-- ################################################################################################# +-- Error handling if not installed or wrong version -- ######################### +if not TOOLS.service_ok() then + local f = SimpleForm("_no_config") + f.title = TOOLS.app_title_main() + f.description = TOOLS.app_description() + f.submit = false + f.reset = false + + local s = f:section(SimpleSection) + + local v = s:option(DummyValue, "_update_needed") + v.rawhtml = true + if TOOLS.service_installed() == "0" then + v.value = [[<h3><strong><br /><font color="red"> ]] + .. translate("Software package '" .. TOOLS.service_name() .. "' is not installed.") + .. [[</font><br /><br /> ]] + .. translate("required") .. [[: ]] .. TOOLS.service_name() .. [[ ]] .. TOOLS.service_required() + .. [[<br /><br /> ]] + .. [[<a href="]] .. DISP.build_url("admin", "system", "packages") ..[[">]] + .. translate("Please install current version !") + .. [[</a><br /> </strong></h3>]] + else + v.value = [[<h3><strong><br /><font color="red"> ]] + .. translate("Software package '" .. TOOLS.service_name() .. "' is outdated.") + .. [[</font><br /><br /> ]] + .. translate("installed") .. [[: ]] .. TOOLS.service_name() .. [[ ]] .. TOOLS.service_installed() + .. [[<br /> ]] + .. translate("required") .. [[: ]] .. TOOLS.service_name() .. [[ ]] .. TOOLS.service_required() + .. [[<br /><br /> ]] + .. [[<a href="]] .. DISP.build_url("admin", "system", "packages") ..[[">]] + .. translate("Please update to current version !") + .. [[</a><br /> </strong></h3>]] + end + + return f +end + +-- ################################################################################################# +-- Error handling if no config, create an empty one -- ######################### +if not NXFS.access("/etc/config/radicale") then + NXFS.writefile("/etc/config/radicale", "") +end + +-- cbi-map -- ################################################################## +local m = Map("radicale") +m.title = TOOLS.app_title_main() +m.description = TOOLS.app_description() +function m.commit_handler(self) + if self.changed then -- changes ? + os.execute("/etc/init.d/radicale reload &") -- reload configuration + end +end + +-- cbi-section "System" -- ##################################################### +local sys = m:section( NamedSection, "_system" ) +sys.title = translate("System") +sys.description = nil +function sys.cfgvalue(self, section) + return "_dummysection" +end + +-- start/stop button ----------------------------------------------------------- +local btn = sys:option(DummyValue, "_startstop") +btn.template = "radicale/btn_startstop" +btn.inputstyle = nil +btn.rmempty = true +btn.title = translate("Start / Stop") +btn.description = translate("Start/Stop Radicale server") +function btn.cfgvalue(self, section) + local pid = TOOLS.get_pid(true) + if pid > 0 then + btn.inputtitle = "PID: " .. pid + btn.inputstyle = "reset" + btn.disabled = false + else + btn.inputtitle = translate("Start") + btn.inputstyle = "apply" + btn.disabled = false + end + return true +end + +-- enabled --------------------------------------------------------------------- +local ena = sys:option(Flag, "_enabled") +ena.title = translate("Auto-start") +ena.description = translate("Enable/Disable auto-start of Radicale on system start-up and interface events") +ena.orientation = "horizontal" -- put description under the checkbox +ena.rmempty = false -- we need write +function ena.cfgvalue(self, section) + return (SYS.init.enabled("radicale")) and "1" or "0" +end +function ena.write(self, section, value) + if value == "1" then + return SYS.init.enable("radicale") + else + return SYS.init.disable("radicale") + end +end + +-- cbi-section "Server" -- ##################################################### +local srv = m:section( NamedSection, "server", "setting" ) +srv.title = translate("Server") +srv.description = nil +function srv.cfgvalue(self, section) + if not self.map:get(section) then -- section might not exist + self.map:set(section, nil, self.sectiontype) + end + return self.map:get(section) +end + +-- hosts ----------------------------------------------------------------------- +local sh = srv:option( DynamicList, "hosts" ) +sh.title = translate("Address:Port") +sh.description = translate("'Hostname:Port' or 'IPv4:Port' or '[IPv6]:Port' Radicale should listen on") + .. [[<br /><strong>]] + .. translate("Port numbers below 1024 (Privileged ports) are not supported") + .. [[</strong>]] +sh.placeholder = "0.0.0.0:5232" +sh.rmempty = true + +-- realm ----------------------------------------------------------------------- +local alm = srv:option( Value, "realm" ) +alm.title = translate("Logon message") +alm.description = translate("Message displayed in the client when a password is needed.") +alm.default = "Radicale - Password Required" +alm.rmempty = false +function alm.parse(self, section) + AbstractValue.parse(self, section, "true") -- otherwise unspecific validate error +end +function alm.validate(self, value) + if value then + return value + else + return self.default + end +end +function alm.write(self, section, value) + if value ~= self.default then + return self.map:set(section, self.option, value) + else + return self.map:del(section, self.option) + end +end + +-- ssl ------------------------------------------------------------------------- +local ssl = srv:option( Flag, "ssl" ) +ssl.title = translate("Enable HTTPS") +ssl.description = nil +ssl.rmempty = false +function ssl.parse(self, section) + TOOLS.flag_parse(self, section) +end +function ssl.write(self, section, value) + if value == "0" then -- delete all if not https enabled + self.map:del(section, "protocol") -- protocol + self.map:del(section, "certificate") -- certificate + self.map:del(section, "key") -- private key + self.map:del(section, "ciphers") -- ciphers + return self.map:del(section, self.option) + else + return self.map:set(section, self.option, value) + end +end + +-- protocol -------------------------------------------------------------------- +local prt = srv:option( ListValue, "protocol" ) +prt.title = translate("SSL Protocol") +prt.description = translate("'AUTO' selects the highest protocol version that client and server support.") +prt.widget = "select" +prt.default = "PROTOCOL_SSLv23" +prt:depends ("ssl", "1") +prt:value ("PROTOCOL_SSLv23", translate("AUTO")) +prt:value ("PROTOCOL_SSLv2", "SSL v2") +prt:value ("PROTOCOL_SSLv3", "SSL v3") +prt:value ("PROTOCOL_TLSv1", "TLS v1") +prt:value ("PROTOCOL_TLSv1_1", "TLS v1.1") +prt:value ("PROTOCOL_TLSv1_2", "TLS v1.2") + +-- certificate ----------------------------------------------------------------- +local crt = srv:option( Value, "certificate" ) +crt.title = translate("Certificate file") +crt.description = translate("Full path and file name of certificate") +crt.placeholder = "/etc/radicale/ssl/server.crt" +crt.rmempty = false -- force validate/write +crt:depends ("ssl", "1") +function crt.parse(self, section) + local _ssl = ssl:formvalue(section) or "0" + local novld = (_ssl == "0") + AbstractValue.parse(self, section, novld) -- otherwise unspecific validate error +end +function crt.validate(self, value) + local _ssl = ssl:formvalue(srv.section) or "0" + if _ssl == "0" then + return "" -- ignore if not https enabled + end + if value then -- otherwise errors in datatype check + if DTYP.file(value) then + return value + else + return nil, self.title .. " - " .. translate("File not found !") + end + else + return nil, self.title .. " - " .. translate("Path/File required !") + end +end +function crt.write(self, section, value) + if not value or #value == 0 then + return self.map:del(section, self.option) + else + return self.map:set(section, self.option, value) + end +end + +-- key ------------------------------------------------------------------------- +local key = srv:option( Value, "key" ) +key.title = translate("Private key file") +key.description = translate("Full path and file name of private key") +key.placeholder = "/etc/radicale/ssl/server.key" +key.rmempty = false -- force validate/write +key:depends ("ssl", "1") +function key.parse(self, section) + local _ssl = ssl:formvalue(section) or "0" + local novld = (_ssl == "0") + AbstractValue.parse(self, section, novld) -- otherwise unspecific validate error +end +function key.validate(self, value) + local _ssl = ssl:formvalue(srv.section) or "0" + if _ssl == "0" then + return "" -- ignore if not https enabled + end + if value then -- otherwise errors in datatype check + if DTYP.file(value) then + return value + else + return nil, self.title .. " - " .. translate("File not found !") + end + else + return nil, self.title .. " - " .. translate("Path/File required !") + end +end +function key.write(self, section, value) + if not value or #value == 0 then + return self.map:del(section, self.option) + else + return self.map:set(section, self.option, value) + end +end + +-- ciphers --------------------------------------------------------------------- +--local cip = srv:option( Value, "ciphers" ) +--cip.title = translate("Ciphers") +--cip.description = translate("OPTIONAL: See python's ssl module for available ciphers") +--cip.rmempty = true +--cip:depends ("ssl", "1") + +-- cbi-section "Authentication" -- ############################################# +local aut = m:section( NamedSection, "auth", "setting" ) +aut.title = translate("Authentication") +aut.description = translate("Authentication method to allow access to Radicale server.") +function aut.cfgvalue(self, section) + if not self.map:get(section) then -- section might not exist + self.map:set(section, nil, self.sectiontype) + end + return self.map:get(section) +end + +-- type ----------------------------------------------------------------------- +local aty = aut:option( ListValue, "type" ) +aty.title = translate("Authentication method") +aty.description = nil +aty.widget = "select" +aty.default = "None" +aty:value ("None", translate("None")) +aty:value ("htpasswd", translate("htpasswd file")) +--aty:value ("IMAP", "IMAP") -- The IMAP authentication module relies on the imaplib module. +--aty:value ("LDAP", "LDAP") -- The LDAP authentication module relies on the python-ldap module. +--aty:value ("PAM", "PAM") -- The PAM authentication module relies on the python-pam module. +--aty:value ("courier", "courier") +--aty:value ("HTTP", "HTTP") -- The HTTP authentication module relies on the requests module +--aty:value ("remote_user", "remote_user") +--aty:value ("custom", translate("custom")) +function aty.write(self, section, value) + if value ~= "htpasswd" then + self.map:del(section, "htpasswd_encryption") + elseif value ~= "IMAP" then + self.map:del(section, "imap_hostname") + self.map:del(section, "imap_port") + self.map:del(section, "imap_ssl") + end + if value ~= self.default then + return self.map:set(section, self.option, value) + else + return self.map:del(section, self.option) + end +end + +-- htpasswd_encryption --------------------------------------------------------- +local hte = aut:option( ListValue, "htpasswd_encryption" ) +hte.title = translate("Encryption method") +hte.description = nil +hte.widget = "select" +hte.default = "crypt" +hte:depends ("type", "htpasswd") +hte:value ("crypt", translate("crypt")) +hte:value ("plain", translate("plain")) +hte:value ("sha1", translate("SHA-1")) +hte:value ("ssha", translate("salted SHA-1")) + +-- htpasswd_file (dummy) ------------------------------------------------------- +local htf = aut:option( DummyValue, "_htf" ) +htf.title = translate("htpasswd file") +htf.description = [[<strong>]] + .. translate("Read only!") + .. [[</strong> ]] + .. translate("Radicale uses '/etc/radicale/users' as htpasswd file.") + .. [[<br /><a href="]] + .. DISP.build_url("admin", "services", "radicale", "edit") .. [[/auth]] + .. [[">]] + .. translate("To edit the file follow this link!") + .. [[</a>]] +htf.keylist = {} -- required by template +htf.vallist = {} -- required by template +htf.template = "radicale/ro_value" +htf.readonly = true +htf:depends ("type", "htpasswd") +function htf.cfgvalue() + return "/etc/radicale/users" +end + +-- cbi-section "Rights" -- ##################################################### +local rig = m:section( NamedSection, "rights", "setting" ) +rig.title = translate("Rights") +rig.description = translate("Control the access to data collections.") +function rig.cfgvalue(self, section) + if not self.map:get(section) then -- section might not exist + self.map:set(section, nil, self.sectiontype) + end + return self.map:get(section) +end + +-- type ----------------------------------------------------------------------- +local rty = rig:option( ListValue, "type" ) +rty.title = translate("Rights backend") +rty.description = nil +rty.widget = "select" +rty.default = "None" +rty:value ("None", translate("Full access for everybody (including anonymous)")) +rty:value ("authenticated", translate("Full access for authenticated Users") ) +rty:value ("owner_only", translate("Full access for Owner only") ) +rty:value ("owner_write", translate("Owner allow write, authenticated users allow read") ) +rty:value ("from_file", translate("Rights are based on a regexp-based file") ) +--rty:value ("custom", "Custom handler") +function rty.write(self, section, value) + if value ~= "custom" then + self.map:del(section, "custom_handler") + end + if value ~= self.default then + return self.map:set(section, self.option, value) + else + return self.map:del(section, self.option) + end +end + +-- from_file (dummy) ----------------------------------------------------------- +local rtf = rig:option( DummyValue, "_rtf" ) +rtf.title = translate("RegExp file") +rtf.description = [[<strong>]] + .. translate("Read only!") + .. [[</strong> ]] + .. translate("Radicale uses '/etc/radicale/rights' as regexp-based file.") + .. [[<br /><a href="]] + .. DISP.build_url("admin", "services", "radicale", "edit") .. [[/rights]] + .. [[">]] + .. translate("To edit the file follow this link!") + .. [[</a>]] +rtf.keylist = {} -- required by template +rtf.vallist = {} -- required by template +rtf.template = "radicale/ro_value" +rtf.readonly = true +rtf:depends ("type", "from_file") +function rtf.cfgvalue() + return "/etc/radicale/rights" +end + +-- cbi-section "Storage" -- #################################################### +local sto = m:section( NamedSection, "storage", "setting" ) +sto.title = translate("Storage") +sto.description = nil +function sto.cfgvalue(self, section) + if not self.map:get(section) then -- section might not exist + self.map:set(section, nil, self.sectiontype) + end + return self.map:get(section) +end + +-- type ----------------------------------------------------------------------- +local sty = sto:option( ListValue, "type" ) +sty.title = translate("Storage backend") +sty.description = translate("WARNING: Only 'File-system' is documented and tested by Radicale development") +sty.widget = "select" +sty.default = "filesystem" +sty:value ("filesystem", translate("File-system")) +--sty:value ("multifilesystem", translate("") ) +--sty:value ("database", translate("Database") ) +--sty:value ("custom", translate("Custom") ) +function sty.write(self, section, value) + if value ~= "filesystem" then + self.map:del(section, "filesystem_folder") + end + if value ~= self.default then + return self.map:set(section, self.option, value) + else + return self.map:del(section, self.option) + end +end + +--filesystem_folder ------------------------------------------------------------ +local sfi = sto:option( Value, "filesystem_folder" ) +sfi.title = translate("Directory") +sfi.description = nil +sfi.default = "/srv/radicale" +sfi.rmempty = false -- force validate/write +sfi:depends ("type", "filesystem") +function sfi.parse(self, section) + local _typ = sty:formvalue(sto.section) or "" + local novld = (_typ ~= "filesystem") + AbstractValue.parse(self, section, novld) -- otherwise unspecific validate error +end +function sfi.validate(self, value) + local _typ = sty:formvalue(sto.section) or "" + if _typ ~= "filesystem" then + return "" -- ignore if not htpasswd + end + if value then -- otherwise errors in datatype check + if DTYP.directory(value) then + return value + else + return nil, self.title .. " - " .. translate("Directory not exists/found !") + end + else + return nil, self.title .. " - " .. translate("Directory required !") + end +end + +-- cbi-section "Logging" -- #################################################### +local log = m:section( NamedSection, "logger", "logging" ) +log.title = translate("Logging") +log.description = nil +function log.cfgvalue(self, section) + if not self.map:get(section) then -- section might not exist + self.map:set(section, nil, self.sectiontype) + end + return self.map:get(section) +end + +-- console_level --------------------------------------------------------------- +local lco = log:option( ListValue, "console_level" ) +lco.title = translate("Console Log level") +lco.description = nil +lco.widget = "select" +lco.default = "ERROR" +lco:value ("DEBUG", translate("Debug")) +lco:value ("INFO", translate("Info") ) +lco:value ("WARNING", translate("Warning") ) +lco:value ("ERROR", translate("Error") ) +lco:value ("CRITICAL", translate("Critical") ) +function lco.write(self, section, value) + if value ~= self.default then + return self.map:set(section, self.option, value) + else + return self.map:del(section, self.option) + end +end + +-- syslog_level ---------------------------------------------------------------- +local lsl = log:option( ListValue, "syslog_level" ) +lsl.title = translate("Syslog Log level") +lsl.description = nil +lsl.widget = "select" +lsl.default = "WARNING" +lsl:value ("DEBUG", translate("Debug")) +lsl:value ("INFO", translate("Info") ) +lsl:value ("WARNING", translate("Warning") ) +lsl:value ("ERROR", translate("Error") ) +lsl:value ("CRITICAL", translate("Critical") ) +function lsl.write(self, section, value) + if value ~= self.default then + return self.map:set(section, self.option, value) + else + return self.map:del(section, self.option) + end +end + +-- file_level ------------------------------------------------------------------ +local lfi = log:option( ListValue, "file_level" ) +lfi.title = translate("File Log level") +lfi.description = nil +lfi.widget = "select" +lfi.default = "INFO" +lfi:value ("DEBUG", translate("Debug")) +lfi:value ("INFO", translate("Info") ) +lfi:value ("WARNING", translate("Warning") ) +lfi:value ("ERROR", translate("Error") ) +lfi:value ("CRITICAL", translate("Critical") ) +function lfi.write(self, section, value) + if value ~= self.default then + return self.map:set(section, self.option, value) + else + return self.map:del(section, self.option) + end +end + +-- file_path ------------------------------------------------------------------- +local lfp = log:option( Value, "file_path" ) +lfp.title = translate("Log-file directory") +lfp.description = translate("Directory where the rotating log-files are stored") + .. [[<br /><a href="]] + .. DISP.build_url("admin", "services", "radicale", "edit") .. [[/logger]] + .. [[">]] + .. translate("To view latest log file follow this link!") + .. [[</a>]] +lfp.default = "/var/log/radicale" +function lfp.write(self, section, value) + if value ~= self.default then + return self.map:set(section, self.option, value) + else + return self.map:del(section, self.option) + end +end + +-- file_maxbytes --------------------------------------------------------------- +local lmb = log:option( Value, "file_maxbytes" ) +lmb.title = translate("Log-file size") +lmb.description = translate("Maximum size of each rotation log-file.") + .. [[<br /><strong>]] + .. translate("Setting this parameter to '0' will disable rotation of log-file.") + .. [[</strong>]] +lmb.default = "8196" +lmb.rmempty = false +function lmb.validate(self, value) + if value then -- otherwise errors in datatype check + if DTYP.uinteger(value) then + return value + else + return nil, self.title .. " - " .. translate("Value is not an Integer >= 0 !") + end + else + return nil, self.title .. " - " .. translate("Value required ! Integer >= 0 !") + end +end +function lmb.write(self, section, value) + if value ~= self.default then + return self.map:set(section, self.option, value) + else + return self.map:del(section, self.option) + end +end + +-- file_backupcount ------------------------------------------------------------ +local lbc = log:option( Value, "file_backupcount" ) +lbc.title = translate("Log-backup Count") +lbc.description = translate("Number of backup files of log to create.") + .. [[<br /><strong>]] + .. translate("Setting this parameter to '0' will disable rotation of log-file.") + .. [[</strong>]] +lbc.default = "1" +lbc.rmempty = false +function lbc.validate(self, value) + if value then -- otherwise errors in datatype check + if DTYP.uinteger(value) then + return value + else + return nil, self.title .. " - " .. translate("Value is not an Integer >= 0 !") + end + else + return nil, self.title .. " - " .. translate("Value required ! Integer >= 0 !") + end +end +function lbc.write(self, section, value) + if value ~= self.default then + return self.map:set(section, self.option, value) + else + return self.map:del(section, self.option) + end +end + +-- cbi-section "Encoding" -- ################################################### +local enc = m:section( NamedSection, "encoding", "setting" ) +enc.title = translate("Encoding") +enc.description = translate("Change here the encoding Radicale will use instead of 'UTF-8' " + .. "for responses to the client and/or to store data inside collections.") +function enc.cfgvalue(self, section) + if not self.map:get(section) then -- section might not exist + self.map:set(section, nil, self.sectiontype) + end + return self.map:get(section) +end + +-- request --------------------------------------------------------------------- +local enr = enc:option( Value, "request" ) +enr.title = translate("Response Encoding") +enr.description = translate("Encoding for responding requests.") +enr.default = "utf-8" +enr.optional = true + +-- stock ----------------------------------------------------------------------- +local ens = enc:option( Value, "stock" ) +ens.title = translate("Storage Encoding") +ens.description = translate("Encoding for storing local collections.") +ens.default = "utf-8" +ens.optional = true + +-- cbi-section "Headers" -- #################################################### +local hea = m:section( NamedSection, "headers", "setting" ) +hea.title = translate("Additional HTTP headers") +hea.description = translate("Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources (e.g. fonts, JavaScript, etc.) " + .. "on a web page to be requested from another domain outside the domain from which the resource originated.") +function hea.cfgvalue(self, section) + if not self.map:get(section) then -- section might not exist + self.map:set(section, nil, self.sectiontype) + end + return self.map:get(section) +end + +-- Access_Control_Allow_Origin ------------------------------------------------- +local heo = hea:option( DynamicList, "Access_Control_Allow_Origin" ) +heo.title = translate("Access-Control-Allow-Origin") +heo.description = nil +heo.default = "*" +heo.optional = true + +-- Access_Control_Allow_Methods ------------------------------------------------ +local hem = hea:option( DynamicList, "Access_Control_Allow_Methods" ) +hem.title = translate("Access-Control-Allow-Methods") +hem.description = nil +hem.optional = true + +-- Access_Control_Allow_Headers ------------------------------------------------ +local heh = hea:option( DynamicList, "Access_Control_Allow_Headers" ) +heh.title = translate("Access-Control-Allow-Headers") +heh.description = nil +heh.optional = true + +-- Access_Control_Expose_Headers ----------------------------------------------- +local hee = hea:option( DynamicList, "Access_Control_Expose_Headers" ) +hee.title = translate("Access-Control-Expose-Headers") +hee.description = nil +hee.optional = true + +return m diff --git a/applications/luci-app-radicale/luasrc/view/radicale/btn_startstop.htm b/applications/luci-app-radicale/luasrc/view/radicale/btn_startstop.htm new file mode 100644 index 0000000000..79d1c36297 --- /dev/null +++ b/applications/luci-app-radicale/luasrc/view/radicale/btn_startstop.htm @@ -0,0 +1,49 @@ + +<!-- ++ BEGIN ++ Radicale ++ btn_startstop.htm ++ --> +<script type="text/javascript">//<![CDATA[ + + // show XHR.poll/XHR.get response on button + function _data2elements(x) { + var btn = document.getElementById("cbid.radicale._system._startstop"); + if ( ! btn ) { return; } // security check + if (x.responseText == "0") { + btn.value = "<%:Start%>"; + btn.className = "cbi-button cbi-button-apply"; + btn.disabled = false; + } else { + btn.value = "PID: " + x.responseText; + btn.className = "cbi-button cbi-button-reset"; + btn.disabled = false; + } + } + + // event handler for start/stop button + function onclick_startstop(id) { + // do start/stop + var btnXHR = new XHR(); + btnXHR.get('<%=luci.dispatcher.build_url("admin", "services", "radicale", "startstop")%>', null, + function(x) { _data2elements(x); } + ); + } + + XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "services", "radicale", "status")%>', null, + function(x, data) { _data2elements(x); } + ); + +//]]></script> + +<%+cbi/valueheader%> + +<% if self:cfgvalue(section) ~= false then +-- We need to garantie that function cfgvalue run first to set missing parameters +%> + <!-- style="font-size: 100%;" needed for openwrt theme to fix font size --> + <!-- type="button" onclick="..." enable standard onclick functionalty --> + <input class="cbi-button cbi-input-<%=self.inputstyle or "button" %>" style="font-size: 100%;" type="button" onclick="onclick_startstop(this.id)" + <%= + attr("name", section) .. attr("id", cbid) .. attr("value", self.inputtitle) .. ifattr(self.disabled, "disabled") + %> /> +<% end %> + +<%+cbi/valuefooter%> +<!-- ++ END ++ Radicale ++ btn_startstop.htm ++ --> diff --git a/applications/luci-app-radicale/luasrc/view/radicale/ro_value.htm b/applications/luci-app-radicale/luasrc/view/radicale/ro_value.htm new file mode 100644 index 0000000000..6e05206aa1 --- /dev/null +++ b/applications/luci-app-radicale/luasrc/view/radicale/ro_value.htm @@ -0,0 +1,35 @@ +<%+cbi/valueheader%> + <input type="<%=self.password and 'password" class="cbi-input-password' or 'text" class="cbi-input-text' %>" onchange="cbi_d_update(this.id)"<%= + attr("name", cbid) .. attr("id", cbid) .. attr("value", self:cfgvalue(section) or self.default) .. + ifattr(self.size, "size") .. ifattr(self.placeholder, "placeholder") .. ifattr(self.readonly, "readonly") + %> /> + <% if self.password then %><img src="<%=resource%>/cbi/reload.gif" style="vertical-align:middle" title="<%:Reveal/hide password%>" onclick="var e = document.getElementById('<%=cbid%>'); e.type = (e.type=='password') ? 'text' : 'password';" /><% end %> + <% if #self.keylist > 0 or self.datatype then -%> + <script type="text/javascript">//<![CDATA[ + <% if #self.keylist > 0 then -%> + cbi_combobox_init('<%=cbid%>', { + <%- + for i, k in ipairs(self.keylist) do + -%> + <%-=string.format("%q", k) .. ":" .. string.format("%q", self.vallist[i])-%> + <%-if i<#self.keylist then-%>,<%-end-%> + <%- + end + -%> + }, '<%- if not self.rmempty and not self.optional then -%> + <%-: -- Please choose -- -%> + <%- elseif self.placeholder then -%> + <%-= pcdata(self.placeholder) -%> + <%- end -%>', ' + <%- if self.combobox_manual then -%> + <%-=self.combobox_manual-%> + <%- else -%> + <%-: -- custom -- -%> + <%- end -%>'); + <%- end %> + <% if self.datatype then -%> + cbi_validate_field('<%=cbid%>', <%=tostring((self.optional or self.rmempty) == true)%>, '<%=self.datatype:gsub("'", "\\'")%>'); + <%- end %> + //]]></script> + <% end -%> +<%+cbi/valuefooter%> diff --git a/applications/luci-app-radicale/po/de/radicale.po b/applications/luci-app-radicale/po/de/radicale.po new file mode 100644 index 0000000000..57850dc109 --- /dev/null +++ b/applications/luci-app-radicale/po/de/radicale.po @@ -0,0 +1,435 @@ +msgid "" +msgstr "" +"Project-Id-Version: luci-app-radicale\n" +"POT-Creation-Date: 2015-05-02 19:32+0100\n" +"PO-Revision-Date: 2015-05-02 22:43+0100\n" +"Last-Translator: Christian Schoenebeck <christian.schoenebeck@gmail.com>\n" +"Language-Team: \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.7.5\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-SourceCharset: UTF-8\n" + +msgid "" +"'AUTO' selects the highest protocol version that client and server support." +msgstr "" +"'AUTO' wählt die höchste Protokollversion, die Client und Server " +"unterstützen." + +msgid "" +"'Hostname:Port' or 'IPv4:Port' or '[IPv6]:Port' Radicale should listen on" +msgstr "" +"'Hostname:Port' oder 'IPv4:Port' oder '[IPv6]:Port' die Radicale überwachen " +"soll." + +msgid "-- Please choose --" +msgstr "-- Bitte auswählen --" + +msgid "-- custom --" +msgstr "-- benutzerdefiniert --" + +msgid "AUTO" +msgstr "AUTO" + +msgid "Access-Control-Allow-Headers" +msgstr "Access-Control-Allow-Headers" + +msgid "Access-Control-Allow-Methods" +msgstr "Access-Control-Allow-Methods" + +msgid "Access-Control-Allow-Origin" +msgstr "Access-Control-Allow-Origin" + +msgid "Access-Control-Expose-Headers" +msgstr "Access-Control-Expose-Headers" + +msgid "Additional HTTP headers" +msgstr "Zusätzliche HTTP headers" + +msgid "Address:Port" +msgstr "Adresse:Port" + +msgid "Authentication" +msgstr "Authentifizierung" + +msgid "" +"Authentication login is matched against the 'user' key, and collection's " +"path is matched against the 'collection' key." +msgstr "" +"Der Login wird gegen die 'user' Schlüssel und die Pfadsammlung gegen die " +"'collection' Schlüssel abgestimmt." + +msgid "Authentication method" +msgstr "Authentifizierungsmethode" + +msgid "Authentication method to allow access to Radicale server." +msgstr "" +"Authentifizierungsmethode um den Zugang zum Radicale Server zu kontrollieren." + +msgid "Auto-start" +msgstr "Autostart" + +msgid "CalDAV/CardDAV" +msgstr "CalDAV/CardDAV" + +msgid "" +"Calendars and address books are available for both local and remote access, " +"possibly limited through authentication policies." +msgstr "" +"Auf Kalender und Adressbücher kann sowohl Lokal als auch Remote zugegriffen " +"werden, soweit nicht durch Authentifizierungsrichtlinien begrenzt." + +msgid "Certificate file" +msgstr "Zertifikat Datei" + +msgid "" +"Change here the encoding Radicale will use instead of 'UTF-8' for responses " +"to the client and/or to store data inside collections." +msgstr "" +"Ändern Sie hier die Zeichenkodierung die Radicale anstelle von \"UTF-8\" für " +"Antworten an den Client und/oder zum Speichern von Daten in einer Sammlung " +"verwendet." + +msgid "Ciphers" +msgstr "Chiffren" + +msgid "Console Log level" +msgstr "Konsole Protokoll Level" + +msgid "Control the access to data collections." +msgstr "Kontrolliert den Zugriff auf die Daten Sammlungen." + +msgid "Critical" +msgstr "Kritisch" + +msgid "" +"Cross-origin resource sharing (CORS) is a mechanism that allows restricted " +"resources (e.g. fonts, JavaScript, etc.) on a web page to be requested from " +"another domain outside the domain from which the resource originated." +msgstr "" +"Cross-Origin Resource Sharing (CORS) ist ein Mechanismus, um Webbrowsern " +"oder auch anderen Webclients Cross-Origin-Requests zu ermöglichen." + +msgid "Custom" +msgstr "Benutzerdefiniert" + +msgid "Database" +msgstr "Datenbank" + +msgid "Debug" +msgstr "Debug" + +msgid "Directory" +msgstr "Verzeichnis" + +msgid "Directory not exists/found !" +msgstr "Verzeichnis nicht gefunden / existiert nicht !" + +msgid "Directory required !" +msgstr "Verzeichnis benötigt !" + +msgid "Directory where the rotating log-files are stored" +msgstr "" +"Verzeichnis in dem die rollierenden Protokolldateien gespeichert werden" + +msgid "Enable HTTPS" +msgstr "Verwende HTTPS" + +msgid "" +"Enable/Disable auto-start of Radicale on system start-up and interface events" +msgstr "" +"Aktiviert/Deaktiviert den Autostart von Radicale beim Systemstart und bei " +"Schnittstellenereignissen." + +msgid "Encoding" +msgstr "Zeichenkodierung" + +msgid "Encoding for responding requests." +msgstr "Zeichenkodierung für die Beantwortung von Anfragen." + +msgid "Encoding for storing local collections." +msgstr "Zeichenkodierung für die Speicherung von lokalen Sammlungen." + +msgid "Encryption method" +msgstr "Verschlüsselungsmethode" + +msgid "Error" +msgstr "Fehler" + +msgid "File '%s' not found !" +msgstr "Datei '%s' wurde nicht gefunden!" + +msgid "File Log level" +msgstr "Datei Protokoll Level" + +msgid "File not found !" +msgstr "Datei nicht gefunden !" + +msgid "File-system" +msgstr "Dateisystem" + +msgid "" +"For example, for the 'user' key, '.+' means 'authenticated user' and '.*' " +"means 'anybody' (including anonymous users)." +msgstr "" +"Beispiel für den 'user' Schlüssel: '. +' bedeutet 'authentifizierten " +"Benutzer' und '. *' bedeutet 'jeder' (einschließlich anonyme Benutzer)." + +msgid "Full access for Owner only" +msgstr "Voller Zugriff nur für den Besitzer" + +msgid "Full access for authenticated Users" +msgstr "Voller Zugriff für authentifizierte Benutzer" + +msgid "Full access for everybody (including anonymous)" +msgstr "Vollzugriff für jedermann (auch anonyme)" + +msgid "Full path and file name of certificate" +msgstr "Vollständiger Pfad und Dateiname der Zertifikat Datei" + +msgid "Full path and file name of private key" +msgstr "Vollständiger Pfad und Dateiname der Privaten Schlüsseldatei" + +msgid "Info" +msgstr "Informationen" + +msgid "Keep in mind to use the correct hashing algorithm !" +msgstr "Denken Sie daran, den korrekten Hash-Algorithmus zu verwenden!" + +msgid "Leading or ending slashes are trimmed from collection's path." +msgstr "" +"Schrägstriche ('/') am Anfang und Ende der Pfadangabe der Sammlung werden " +"von der Pfadangabe abgeschnitten." + +msgid "Log-backup Count" +msgstr "Protokoll Backup Zähler" + +msgid "Log-file Viewer" +msgstr "Protokolldatei Betrachter" + +msgid "Log-file directory" +msgstr "Protokoll-Datei Verzeichnis" + +msgid "Log-file size" +msgstr "Protokoll Dateigröße" + +msgid "Logging" +msgstr "Protokollierung" + +msgid "Logon message" +msgstr "Anmelde-Hinweis" + +msgid "Maximum size of each rotation log-file." +msgstr "Maximale Größe jeder rollierenden Protokoll-Datei." + +msgid "Message displayed in the client when a password is needed." +msgstr "Meldung im Client, wenn ein Kennwort erforderlich ist." + +msgid "NOT installed" +msgstr "nicht installiert" + +msgid "None" +msgstr "Keine" + +msgid "Number of backup files of log to create." +msgstr "Anzahl der Protokoll Backup Dateien, die angelegt werden." + +msgid "OPTIONAL: See python's ssl module for available ciphers" +msgstr "OPTIONAL: Siehe Python SSL-Modul Dokumentation" + +msgid "Owner allow write, authenticated users allow read" +msgstr "" +"Besitzer haben Schreibrechte, Authentifizierten Benutzer dürfen nur lesen." + +msgid "Path/File required !" +msgstr "Pfad/Datei erforderlich!" + +msgid "" +"Place here the 'user:password' pairs for your users which should have access " +"to Radicale." +msgstr "" +"Speichern Sie hier die 'user: password' Paare für die Benutzer, die Zugriff " +"auf Radicale haben sollte." + +msgid "Please install current version !" +msgstr "Installieren Sie bitte die aktuelle Version!" + +msgid "Please press [Reload] button below to reread the file." +msgstr "" +"Bitte drücken Sie die [Neu laden]-Schaltfläche unten, um die Datei neu " +"einzulesen." + +msgid "Please update to current version !" +msgstr "Aktualisieren Sie bitte auf die aktuelle Version!" + +msgid "Port numbers below 1024 (Privileged ports) are not supported" +msgstr "Port Nummern unter 1024 (Privileged Ports) werden nicht unterstützt." + +msgid "Private key file" +msgstr "Private Schlüssel Datei" + +msgid "Radicale CalDAV/CardDAV Server" +msgstr "Radicale CalDAV/CardDAV Dienst" + +msgid "Radicale uses '/etc/radicale/rights' as regexp-based file." +msgstr "Radicale verwendet '/etc/radicale/rights' als RegExp-basierte Datei." + +msgid "Radicale uses '/etc/radicale/users' as htpasswd file." +msgstr "Radicale verwendet 'etc/radicale/users' als htpasswd Datei." + +msgid "Read only!" +msgstr "Nur lesbar!" + +msgid "RegExp file" +msgstr "RegExp Datei" + +msgid "Reload" +msgstr "Neu laden" + +msgid "Response Encoding" +msgstr "Antwort Zeichenkodierung" + +msgid "Reveal/hide password" +msgstr "Passwort zeigen/verstecken" + +msgid "Rights" +msgstr "Zugriffsrechte" + +msgid "Rights are based on a regexp-based file" +msgstr "Zugriff basiert auf RegExp-basierter Datei." + +msgid "Rights backend" +msgstr "Zugagsverwaltung" + +msgid "SHA-1" +msgstr "SHA-1" + +msgid "SSL Protocol" +msgstr "SSL Protokol" + +msgid "Save" +msgstr "Speichern" + +msgid "Section names are only used for naming the rule." +msgstr "Abschnittsnamen werden nur für die Benennung der Regel verwendet." + +msgid "Server" +msgstr "Server" + +msgid "Setting this parameter to '0' will disable rotation of log-file." +msgstr "" +"Wenn dieser Parameter auf '0' gesetzt wird, wird die Protokolldatei nicht " +"mehr rolliert!" + +msgid "Software package '" +msgstr "Software Packet '" + +msgid "Start" +msgstr "Start" + +msgid "Start / Stop" +msgstr "Start / Stopp" + +msgid "Start/Stop Radicale server" +msgstr "Start / Stopp Radicale Dienst" + +msgid "Storage" +msgstr "Datenspeicher" + +msgid "Storage Encoding" +msgstr "Datenspeicher Kodierung" + +msgid "Storage backend" +msgstr "Datenspeicher Verwaltung" + +msgid "Syslog Log level" +msgstr "Systemlog Level" + +msgid "System" +msgstr "System" + +msgid "" +"The Radicale Project is a complete CalDAV (calendar) and CardDAV (contact) " +"server solution." +msgstr "" +"Das Raidcale Projekt bietet eine vollständige CalDAV (Kalender) und CardDAV " +"(Adressbuch) Server Lösung." + +msgid "" +"They can be viewed and edited by calendar and contact clients on mobile " +"phones or computers." +msgstr "" +"Diese können von Kalender- und Adressbuch-Anwendungen auf mobilen Endgeräten " +"und Computern angezeigt und bearbeitet werden." + +msgid "To edit the file follow this link!" +msgstr "Um die Datei zu bearbeiten, folgend Sie dieser Verknüpfung!" + +msgid "To view latest log file follow this link!" +msgstr "" +"Zur Anzeige der letzten Protokolldatei, folgen Sie dieser Verknüpfung !" + +msgid "Value is not an Integer >= 0 !" +msgstr "Eingabe ist keine Ganzzahl >= 0 !" + +msgid "Value required ! Integer >= 0 !" +msgstr "Eingabe erforderlich ! Ganzzahl >= 0 !" + +msgid "Version" +msgstr "Version" + +msgid "Version Information" +msgstr "Versionsinformationen" + +msgid "" +"WARNING: Only 'File-system' is documented and tested by Radicale development" +msgstr "" +"WARNUNG: Nur 'File-system' ist vom Radicale Entwicklerteam derzeit " +"dokumentiert und getestet." + +msgid "Warning" +msgstr "Warnung" + +msgid "" +"You can also get groups from the user regex in the collection with {0}, {1}, " +"etc." +msgstr "" + +msgid "" +"You can use Python's ConfigParser interpolation values %(login)s and " +"%(path)s." +msgstr "" +"Sie können Python ConfigParser Werte '%(login)s' und '%(path)s' verwenden." + +msgid "crypt" +msgstr "crypt" + +msgid "custom" +msgstr "benutzerdefiniert" + +msgid "htpasswd file" +msgstr "htpasswd Datei" + +msgid "installed" +msgstr "installiert" + +msgid "or higher" +msgstr "oder höher" + +msgid "plain" +msgstr "unverschlüsselt" + +msgid "required" +msgstr "erforderlich" + +msgid "salted SHA-1" +msgstr "Salted SHA-1" + +#~ msgid "File" +#~ msgstr "Datei" + +#~ msgid "not found !" +#~ msgstr "nicht gefunden !" diff --git a/applications/luci-app-radicale/po/templates/radicale.pot b/applications/luci-app-radicale/po/templates/radicale.pot new file mode 100644 index 0000000000..c5e079715f --- /dev/null +++ b/applications/luci-app-radicale/po/templates/radicale.pot @@ -0,0 +1,381 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +msgid "" +"'AUTO' selects the highest protocol version that client and server support." +msgstr "" + +msgid "" +"'Hostname:Port' or 'IPv4:Port' or '[IPv6]:Port' Radicale should listen on" +msgstr "" + +msgid "-- Please choose --" +msgstr "" + +msgid "-- custom --" +msgstr "" + +msgid "AUTO" +msgstr "" + +msgid "Access-Control-Allow-Headers" +msgstr "" + +msgid "Access-Control-Allow-Methods" +msgstr "" + +msgid "Access-Control-Allow-Origin" +msgstr "" + +msgid "Access-Control-Expose-Headers" +msgstr "" + +msgid "Additional HTTP headers" +msgstr "" + +msgid "Address:Port" +msgstr "" + +msgid "Authentication" +msgstr "" + +msgid "" +"Authentication login is matched against the 'user' key, and collection's " +"path is matched against the 'collection' key." +msgstr "" + +msgid "Authentication method" +msgstr "" + +msgid "Authentication method to allow access to Radicale server." +msgstr "" + +msgid "Auto-start" +msgstr "" + +msgid "CalDAV/CardDAV" +msgstr "" + +msgid "" +"Calendars and address books are available for both local and remote access, " +"possibly limited through authentication policies." +msgstr "" + +msgid "Certificate file" +msgstr "" + +msgid "" +"Change here the encoding Radicale will use instead of 'UTF-8' for responses " +"to the client and/or to store data inside collections." +msgstr "" + +msgid "Ciphers" +msgstr "" + +msgid "Console Log level" +msgstr "" + +msgid "Control the access to data collections." +msgstr "" + +msgid "Critical" +msgstr "" + +msgid "" +"Cross-origin resource sharing (CORS) is a mechanism that allows restricted " +"resources (e.g. fonts, JavaScript, etc.) on a web page to be requested from " +"another domain outside the domain from which the resource originated." +msgstr "" + +msgid "Custom" +msgstr "" + +msgid "Database" +msgstr "" + +msgid "Debug" +msgstr "" + +msgid "Directory" +msgstr "" + +msgid "Directory not exists/found !" +msgstr "" + +msgid "Directory required !" +msgstr "" + +msgid "Directory where the rotating log-files are stored" +msgstr "" + +msgid "Enable HTTPS" +msgstr "" + +msgid "" +"Enable/Disable auto-start of Radicale on system start-up and interface events" +msgstr "" + +msgid "Encoding" +msgstr "" + +msgid "Encoding for responding requests." +msgstr "" + +msgid "Encoding for storing local collections." +msgstr "" + +msgid "Encryption method" +msgstr "" + +msgid "Error" +msgstr "" + +msgid "File '%s' not found !" +msgstr "" + +msgid "File Log level" +msgstr "" + +msgid "File not found !" +msgstr "" + +msgid "File-system" +msgstr "" + +msgid "" +"For example, for the 'user' key, '.+' means 'authenticated user' and '.*' " +"means 'anybody' (including anonymous users)." +msgstr "" + +msgid "Full access for Owner only" +msgstr "" + +msgid "Full access for authenticated Users" +msgstr "" + +msgid "Full access for everybody (including anonymous)" +msgstr "" + +msgid "Full path and file name of certificate" +msgstr "" + +msgid "Full path and file name of private key" +msgstr "" + +msgid "Info" +msgstr "" + +msgid "Keep in mind to use the correct hashing algorithm !" +msgstr "" + +msgid "Leading or ending slashes are trimmed from collection's path." +msgstr "" + +msgid "Log-backup Count" +msgstr "" + +msgid "Log-file Viewer" +msgstr "" + +msgid "Log-file directory" +msgstr "" + +msgid "Log-file size" +msgstr "" + +msgid "Logging" +msgstr "" + +msgid "Logon message" +msgstr "" + +msgid "Maximum size of each rotation log-file." +msgstr "" + +msgid "Message displayed in the client when a password is needed." +msgstr "" + +msgid "NOT installed" +msgstr "" + +msgid "None" +msgstr "" + +msgid "Number of backup files of log to create." +msgstr "" + +msgid "OPTIONAL: See python's ssl module for available ciphers" +msgstr "" + +msgid "Owner allow write, authenticated users allow read" +msgstr "" + +msgid "Path/File required !" +msgstr "" + +msgid "" +"Place here the 'user:password' pairs for your users which should have access " +"to Radicale." +msgstr "" + +msgid "Please install current version !" +msgstr "" + +msgid "Please press [Reload] button below to reread the file." +msgstr "" + +msgid "Please update to current version !" +msgstr "" + +msgid "Port numbers below 1024 (Privileged ports) are not supported" +msgstr "" + +msgid "Private key file" +msgstr "" + +msgid "Radicale CalDAV/CardDAV Server" +msgstr "" + +msgid "Radicale uses '/etc/radicale/rights' as regexp-based file." +msgstr "" + +msgid "Radicale uses '/etc/radicale/users' as htpasswd file." +msgstr "" + +msgid "Read only!" +msgstr "" + +msgid "RegExp file" +msgstr "" + +msgid "Reload" +msgstr "" + +msgid "Response Encoding" +msgstr "" + +msgid "Reveal/hide password" +msgstr "" + +msgid "Rights" +msgstr "" + +msgid "Rights are based on a regexp-based file" +msgstr "" + +msgid "Rights backend" +msgstr "" + +msgid "SHA-1" +msgstr "" + +msgid "SSL Protocol" +msgstr "" + +msgid "Save" +msgstr "" + +msgid "Section names are only used for naming the rule." +msgstr "" + +msgid "Server" +msgstr "" + +msgid "Setting this parameter to '0' will disable rotation of log-file." +msgstr "" + +msgid "Software package '" +msgstr "" + +msgid "Start" +msgstr "" + +msgid "Start / Stop" +msgstr "" + +msgid "Start/Stop Radicale server" +msgstr "" + +msgid "Storage" +msgstr "" + +msgid "Storage Encoding" +msgstr "" + +msgid "Storage backend" +msgstr "" + +msgid "Syslog Log level" +msgstr "" + +msgid "System" +msgstr "" + +msgid "" +"The Radicale Project is a complete CalDAV (calendar) and CardDAV (contact) " +"server solution." +msgstr "" + +msgid "" +"They can be viewed and edited by calendar and contact clients on mobile " +"phones or computers." +msgstr "" + +msgid "To edit the file follow this link!" +msgstr "" + +msgid "To view latest log file follow this link!" +msgstr "" + +msgid "Value is not an Integer >= 0 !" +msgstr "" + +msgid "Value required ! Integer >= 0 !" +msgstr "" + +msgid "Version" +msgstr "" + +msgid "Version Information" +msgstr "" + +msgid "" +"WARNING: Only 'File-system' is documented and tested by Radicale development" +msgstr "" + +msgid "Warning" +msgstr "" + +msgid "" +"You can also get groups from the user regex in the collection with {0}, {1}, " +"etc." +msgstr "" + +msgid "" +"You can use Python's ConfigParser interpolation values %(login)s and " +"%(path)s." +msgstr "" + +msgid "crypt" +msgstr "" + +msgid "custom" +msgstr "" + +msgid "htpasswd file" +msgstr "" + +msgid "installed" +msgstr "" + +msgid "or higher" +msgstr "" + +msgid "plain" +msgstr "" + +msgid "required" +msgstr "" + +msgid "salted SHA-1" +msgstr "" diff --git a/applications/luci-app-radicale/root/etc/uci-defaults/luci-radicale b/applications/luci-app-radicale/root/etc/uci-defaults/luci-radicale new file mode 100755 index 0000000000..333ca65f45 --- /dev/null +++ b/applications/luci-app-radicale/root/etc/uci-defaults/luci-radicale @@ -0,0 +1,12 @@ +#!/bin/sh + +# no longer needed for "Save and Apply" to restart radicale +# luci-app-radicale calls /etc/init.d/radicale reload +uci -q batch <<-EOF >/dev/null + delete ucitrack.@radicale[-1] + commit ucitrack +EOF + +rm -f /tmp/luci-indexcache + +exit 0 |