X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=applications%2Fluci-app-radicale%2Fluasrc%2Fmodel%2Fcbi%2Fradicale.lua;fp=applications%2Fluci-app-radicale%2Fluasrc%2Fmodel%2Fcbi%2Fradicale.lua;h=8abb68869d3158fafb4d4f9bffd23dcdf4d3b580;hp=0000000000000000000000000000000000000000;hb=e6804f0a9313d5aa207994a4e3a365d5373c8abf;hpb=36879e9e8658601dc3e86ff9251da3fc3666efe0 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 000000000..8abb68869 --- /dev/null +++ b/applications/luci-app-radicale/luasrc/model/cbi/radicale.lua @@ -0,0 +1,748 @@ +-- Copyright 2015 Christian Schoenebeck +-- 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.") + .. [[
]] + .. translate("Keep in mind to use the correct hashing algorithm !") + .. [[]] + 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.") + .. [[
]] + .. translate("For example, for the 'user' key, '.+' means 'authenticated user'" .. " " + .. "and '.*' means 'anybody' (including anonymous users).") + .. [[
]] + .. translate("Section names are only used for naming the rule.") + .. [[
]] + .. 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 = [[


    ]] + .. translate("Software package '" .. TOOLS.service_name() .. "' is not installed.") + .. [[

      ]] + .. translate("required") .. [[: ]] .. TOOLS.service_name() .. [[ ]] .. TOOLS.service_required() + .. [[

    ]] + .. [[]] + .. translate("Please install current version !") + .. [[
 

]] + else + v.value = [[


    ]] + .. translate("Software package '" .. TOOLS.service_name() .. "' is outdated.") + .. [[

      ]] + .. translate("installed") .. [[: ]] .. TOOLS.service_name() .. [[ ]] .. TOOLS.service_installed() + .. [[
      ]] + .. translate("required") .. [[: ]] .. TOOLS.service_name() .. [[ ]] .. TOOLS.service_required() + .. [[

    ]] + .. [[]] + .. translate("Please update to current version !") + .. [[
 

]] + 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") + .. [[
]] + .. translate("Port numbers below 1024 (Privileged ports) are not supported") + .. [[]] +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 = [[]] + .. translate("Read only!") + .. [[ ]] + .. translate("Radicale uses '/etc/radicale/users' as htpasswd file.") + .. [[
]] + .. translate("To edit the file follow this link!") + .. [[]] +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 = [[]] + .. translate("Read only!") + .. [[ ]] + .. translate("Radicale uses '/etc/radicale/rights' as regexp-based file.") + .. [[
]] + .. translate("To edit the file follow this link!") + .. [[]] +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") + .. [[
]] + .. translate("To view latest log file follow this link!") + .. [[]] +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.") + .. [[
]] + .. translate("Setting this parameter to '0' will disable rotation of log-file.") + .. [[]] +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.") + .. [[
]] + .. translate("Setting this parameter to '0' will disable rotation of log-file.") + .. [[]] +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