Merge pull request #305 from nmav/compression
[project/luci.git] / applications / luci-app-ddns / luasrc / model / cbi / ddns / detail.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
3 -- Copyright 2013 Manuel Munz <freifunk at somakoma dot de>
4 -- Copyright 2014 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
5 -- Licensed to the public under the Apache License 2.0.
6
7 local NX   = require "nixio"
8 local FS   = require "nixio.fs"
9 local SYS  = require "luci.sys"
10 local UTIL = require "luci.util"
11 local DISP = require "luci.dispatcher"
12 local WADM = require "luci.tools.webadmin"
13 local DTYP = require "luci.cbi.datatypes"
14 local DDNS = require "luci.tools.ddns"          -- ddns multiused functions
15
16 -- takeover arguments -- #######################################################
17 section = arg[1]
18
19 -- check supported options -- ##################################################
20 -- saved to local vars here because doing multiple os calls slow down the system
21 has_ipv6   = DDNS.check_ipv6()  -- IPv6 support
22 has_ssl    = DDNS.check_ssl()   -- HTTPS support
23 has_proxy  = DDNS.check_proxy() -- Proxy support
24 has_dnstcp = DDNS.check_bind_host()     -- DNS TCP support
25 has_force  = has_ssl and has_dnstcp     -- Force IP Protocoll
26
27 -- html constants -- ###########################################################
28 font_red = "<font color='red'>"
29 font_off = "</font>"
30 bold_on  = "<strong>"
31 bold_off = "</strong>"
32
33 -- error text constants -- #####################################################
34 err_ipv6_plain = translate("IPv6 not supported") .. " - " ..
35                 translate("please select 'IPv4' address version")
36 err_ipv6_basic = bold_on ..
37                         font_red ..
38                                 translate("IPv6 not supported") ..
39                         font_off ..
40                         "<br />" .. translate("please select 'IPv4' address version") ..
41                  bold_off
42 err_ipv6_other = bold_on ..
43                         font_red ..
44                                 translate("IPv6 not supported") ..
45                         font_off ..
46                         "<br />" .. translate("please select 'IPv4' address version in") .. " " ..
47                         [[<a href="]] ..
48                                         DISP.build_url("admin", "services", "ddns", "detail", section) ..
49                                         "?tab.dns." .. section .. "=basic" ..
50                                 [[">]] ..
51                                 translate("Basic Settings") ..
52                         [[</a>]] ..
53                  bold_off
54
55 function err_tab_basic(self)
56         return translate("Basic Settings") .. " - " .. self.title .. ": "
57 end
58 function err_tab_adv(self)
59         return translate("Advanced Settings") .. " - " .. self.title .. ": "
60 end
61 function err_tab_timer(self)
62         return translate("Timer Settings") .. " - " .. self.title .. ": "
63 end
64
65 -- function to verify settings around ip_source
66 -- will use dynamic_dns_lucihelper to check if
67 -- local IP can be read
68 local function _verify_ip_source()
69         -- section is globally defined here be calling agrument (see above)
70         local _network   = "-"
71         local _url       = "-"
72         local _interface = "-"
73         local _script    = "-"
74         local _proxy     = ""
75
76         local _ipv6   = usev6:formvalue(section)
77         local _source = (_ipv6 == "1")
78                         and src6:formvalue(section)
79                         or  src4:formvalue(section)
80         if _source == "network" then
81                 _network = (_ipv6 == "1")
82                         and ipn6:formvalue(section)
83                         or  ipn4:formvalue(section)
84         elseif _source == "web" then
85                 _url = (_ipv6 == "1")
86                         and iurl6:formvalue(section)
87                         or  iurl4:formvalue(section)
88                 -- proxy only needed for checking url
89                 _proxy = (pxy) and pxy:formvalue(section) or ""
90         elseif _source == "interface" then
91                 _interface = ipi:formvalue(section)
92         elseif _source == "script" then
93                 _script = ips:formvalue(section)
94         end
95
96         local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh get_local_ip ]] ..
97                 _ipv6 .. [[ ]] .. _source .. [[ ]] .. _network .. [[ ]] ..
98                 _url .. [[ ]] .. _interface .. [[ ']] .. _script.. [[' ]] .. _proxy
99         local ret = SYS.call(command)
100
101         if ret == 0 then
102                 return true     -- valid
103         else
104                 return nil      -- invalid
105         end
106 end
107
108 -- cbi-map definition -- #######################################################
109 m = Map("ddns")
110
111 -- first need to close <a> from cbi map template our <a> closed by template
112 m.title = [[</a><a href="]] .. DISP.build_url("admin", "services", "ddns") .. [[">]] ..
113                 translate("Dynamic DNS")
114
115 m.description = translate("Dynamic DNS allows that your router can be reached with " ..
116                         "a fixed hostname while having a dynamically changing " ..
117                         "IP address.")
118
119 m.redirect = DISP.build_url("admin", "services", "ddns")
120
121 m.on_after_commit = function(self)
122         if self.changed then    -- changes ?
123                 local pid = DDNS.get_pid(section)
124                 if pid > 0 then -- running ?
125                         local tmp = NX.kill(pid, 1)     -- send SIGHUP
126                 end
127         end
128 end
129
130 -- read application settings -- ################################################
131 -- date format; if not set use ISO format
132 date_format = m.uci:get(m.config, "global", "date_format") or "%F %R"
133 -- log directory
134 log_dir = m.uci:get(m.config, "global", "log_dir") or "/var/log/ddns"
135
136 -- cbi-section definition -- ###################################################
137 ns = m:section( NamedSection, section, "service",
138         translate("Details for") .. ([[: <strong>%s</strong>]] % section),
139         translate("Configure here the details for selected Dynamic DNS service") )
140 ns.instance = section   -- arg [1]
141 ns:tab("basic", translate("Basic Settings"), nil )
142 ns:tab("advanced", translate("Advanced Settings"), nil )
143 ns:tab("timer", translate("Timer Settings"), nil )
144 ns:tab("logview", translate("Log File Viewer"), nil )
145
146 -- TAB: Basic  #####################################################################################
147 -- enabled  -- #################################################################
148 en = ns:taboption("basic", Flag, "enabled",
149         translate("Enabled"),
150         translate("If this service section is disabled it could not be started." .. "<br />" ..
151                 "Neither from LuCI interface nor from console") )
152 en.orientation = "horizontal"
153 function en.parse(self, section)
154         DDNS.flag_parse(self, section)
155 end
156
157 -- use_ipv6 (NEW)  -- ##########################################################
158 usev6 = ns:taboption("basic", ListValue, "use_ipv6",
159         translate("IP address version"),
160         translate("Defines which IP address 'IPv4/IPv6' is send to the DDNS provider") )
161 usev6.widget  = "radio"
162 usev6.default = "0"
163 usev6:value("0", translate("IPv4-Address") )
164 function usev6.cfgvalue(self, section)
165         local value = AbstractValue.cfgvalue(self, section)
166         if has_ipv6 or (value == "1" and not has_ipv6) then
167                 self:value("1", translate("IPv6-Address") )
168         end
169         if value == "1" and not has_ipv6 then
170                 self.description = err_ipv6_basic
171         end
172         return value
173 end
174 function usev6.validate(self, value)
175         if (value == "1" and has_ipv6) or value == "0" then
176                 return value
177         end
178         return nil, err_tab_basic(self) .. err_ipv6_plain
179 end
180 function usev6.write(self, section, value)
181         if value == "0" then    -- force rmempty
182                 return self.map:del(section, self.option)
183         else
184                 return self.map:set(section, self.option, value)
185         end
186 end
187
188 -- IPv4 - service_name -- ######################################################
189 svc4 = ns:taboption("basic", ListValue, "ipv4_service_name",
190         translate("DDNS Service provider") .. " [IPv4]" )
191 svc4.default    = "-"
192 svc4:depends("use_ipv6", "0")   -- only show on IPv4
193
194 local services4 = { }
195 local fd4 = io.open("/usr/lib/ddns/services", "r")
196
197 if fd4 then
198         local ln
199         repeat
200                 ln = fd4:read("*l")
201                 local s = ln and ln:match('^%s*"([^"]+)"')
202                 if s then services4[#services4+1] = s end
203         until not ln
204         fd4:close()
205 end
206
207 for _, v in UTIL.vspairs(services4) do svc4:value(v) end
208 svc4:value("-", translate("-- custom --") )
209
210 function svc4.cfgvalue(self, section)
211         local v =  DDNS.read_value(self, section, "service_name")
212         if not v or #v == 0 then
213                 return "-"
214         else
215                 return v
216         end
217 end
218 function svc4.validate(self, value)
219         if usev6:formvalue(section) == "0" then -- do only on IPv4
220                 return value
221         else
222                 return ""       -- supress validate error
223         end
224 end
225 function svc4.write(self, section, value)
226         if usev6:formvalue(section) == "0" then -- do only IPv4 here
227                 self.map:del(section, self.option)      -- to be shure
228                 if value ~= "-" then                    -- and write "service_name
229                         self.map:del(section, "update_url")     -- delete update_url
230                         return self.map:set(section, "service_name", value)
231                 else
232                         return self.map:del(section, "service_name")
233                 end
234         end
235 end
236
237 -- IPv6 - service_name -- ######################################################
238 svc6 = ns:taboption("basic", ListValue, "ipv6_service_name",
239         translate("DDNS Service provider") .. " [IPv6]" )
240 svc6.default    = "-"
241 svc6:depends("use_ipv6", "1")   -- only show on IPv6
242 if not has_ipv6 then
243         svc6.description = err_ipv6_basic
244 end
245
246 local services6 = { }
247 local fd6 = io.open("/usr/lib/ddns/services_ipv6", "r")
248
249 if fd6 then
250         local ln
251         repeat
252                 ln = fd6:read("*l")
253                 local s = ln and ln:match('^%s*"([^"]+)"')
254                 if s then services6[#services6+1] = s end
255         until not ln
256         fd6:close()
257 end
258
259 for _, v in UTIL.vspairs(services6) do svc6:value(v) end
260 svc6:value("-", translate("-- custom --") )
261
262 function svc6.cfgvalue(self, section)
263         local v =  DDNS.read_value(self, section, "service_name")
264         if not v or #v == 0 then
265                 return "-"
266         else
267                 return v
268         end
269 end
270 function svc6.validate(self, value)
271         if usev6:formvalue(section) == "1" then -- do only on IPv6
272                 if has_ipv6 then return value end
273                 return nil, err_tab_basic(self) .. err_ipv6_plain
274         else
275                 return ""       -- supress validate error
276         end
277 end
278 function svc6.write(self, section, value)
279         if usev6:formvalue(section) == "1" then -- do only when IPv6
280                 self.map:del(section, self.option)      -- delete "ipv6_service_name" helper
281                 if value ~= "-" then                    -- and write "service_name
282                         self.map:del(section, "update_url")     -- delete update_url
283                         return self.map:set(section, "service_name", value)
284                 else
285                         return self.map:del(section, "service_name")
286                 end
287         end
288 end
289
290 -- IPv4/IPv6 - update_url -- ###################################################
291 uurl = ns:taboption("basic", Value, "update_url",
292         translate("Custom update-URL"),
293         translate("Update URL to be used for updating your DDNS Provider." .. "<br />" ..
294                 "Follow instructions you will find on their WEB page.") )
295 uurl:depends("ipv4_service_name", "-")
296 uurl:depends("ipv6_service_name", "-")
297 function uurl.validate(self, value)
298         local script = ush:formvalue(section)
299
300         if (usev6:formvalue(section) == "0" and svc4:formvalue(section) ~= "-") or
301            (usev6:formvalue(section) == "1" and svc6:formvalue(section) ~= "-") then
302                 return ""       -- suppress validate error
303         elseif not value then
304                 if not script or not (#script > 0) then
305                         return nil, err_tab_basic(self) .. translate("missing / required")
306                 else
307                         return ""       -- suppress validate error / update_script is given
308                 end
309         elseif (#script > 0) then
310                 return nil, err_tab_basic(self) .. translate("either url or script could be set")
311         end
312
313         local url = DDNS.parse_url(value)
314         if not url.scheme == "http" then
315                 return nil, err_tab_basic(self) .. translate("must start with 'http://'")
316         elseif not url.query then
317                 return nil, err_tab_basic(self) .. "<QUERY> " .. translate("missing / required")
318         elseif not url.host then
319                 return nil, err_tab_basic(self) .. "<HOST> " .. translate("missing / required")
320         elseif SYS.call([[nslookup ]] .. url.host .. [[ >/dev/null 2>&1]]) ~= 0 then
321                 return nil, err_tab_basic(self) .. translate("can not resolve host: ") .. url.host
322         end
323
324         return value
325 end
326
327 -- IPv4/IPv6 - update_script -- ################################################
328 ush = ns:taboption("basic", Value, "update_script",
329         translate("Custom update-script"),
330         translate("Custom update script to be used for updating your DDNS Provider.") )
331 ush:depends("ipv4_service_name", "-")
332 ush:depends("ipv6_service_name", "-")
333 function ush.validate(self, value)
334         local url = uurl:formvalue(section)
335
336         if (usev6:formvalue(section) == "0" and svc4:formvalue(section) ~= "-") or
337            (usev6:formvalue(section) == "1" and svc6:formvalue(section) ~= "-") then
338                 return ""       -- suppress validate error
339         elseif not value then
340                 if not url or not (#url > 0) then
341                         return nil, err_tab_basic(self) .. translate("missing / required")
342                 else
343                         return ""       -- suppress validate error / update_url is given
344                 end
345         elseif (#url > 0) then
346                 return nil, err_tab_basic(self) .. translate("either url or script could be set")
347         elseif not FS.access(value) then
348                 return nil, err_tab_basic(self) .. translate("File not found")
349         end
350         return value
351 end
352
353 -- IPv4/IPv6 - domain -- #######################################################
354 dom = ns:taboption("basic", Value, "domain",
355                 translate("Hostname/Domain"),
356                 translate("Replaces [DOMAIN] in Update-URL") )
357 dom.rmempty     = false
358 dom.placeholder = "mypersonaldomain.dyndns.org"
359 function dom.validate(self, value)
360         if not value
361         or not (#value > 0)
362         or not DTYP.hostname(value) then
363                 return nil, err_tab_basic(self) ..      translate("invalid - Sample") .. ": 'mypersonaldomain.dyndns.org'"
364         else
365                 return value
366         end
367 end
368
369 -- IPv4/IPv6 - username -- #####################################################
370 user = ns:taboption("basic", Value, "username",
371                 translate("Username"),
372                 translate("Replaces [USERNAME] in Update-URL") )
373 user.rmempty = false
374 function user.validate(self, value)
375         if not value then
376                 return nil, err_tab_basic(self) .. translate("missing / required")
377         end
378         return value
379 end
380
381 -- IPv4/IPv6 - password -- #####################################################
382 pw = ns:taboption("basic", Value, "password",
383                 translate("Password"),
384                 translate("Replaces [PASSWORD] in Update-URL") )
385 pw.rmempty  = false
386 pw.password = true
387 function pw.validate(self, value)
388         if not value then
389                 return nil, err_tab_basic(self) .. translate("missing / required")
390         end
391         return value
392 end
393
394 -- IPv4/IPv6 - use_https (NEW) -- ##############################################
395 if has_ssl or ( ( m:get(section, "use_https") or "0" ) == "1" ) then
396         https = ns:taboption("basic", Flag, "use_https",
397                 translate("Use HTTP Secure") )
398         https.orientation = "horizontal"
399         https.rmempty = false -- force validate function
400         function https.cfgvalue(self, section)
401                 local value = AbstractValue.cfgvalue(self, section)
402                 if not has_ssl and value == "1" then
403                         self.description = bold_on .. font_red ..
404                                 translate("HTTPS not supported") .. font_off .. "<br />" ..
405                                 translate("please disable") .. " !" .. bold_off
406                 else
407                         self.description = translate("Enable secure communication with DDNS provider")
408                 end
409                 return value
410         end
411         function https.parse(self, section)
412                 DDNS.flag_parse(self, section)
413         end
414         function https.validate(self, value)
415                 if (value == "1" and has_ssl ) or value == "0" then return value end
416                 return nil, err_tab_basic(self) .. translate("HTTPS not supported") .. " !"
417         end
418         function https.write(self, section, value)
419                 if value == "1" then
420                         return self.map:set(section, self.option, value)
421                 else
422                         self.map:del(section, "cacert")
423                         return self.map:del(section, self.option)
424                 end
425         end
426 end
427
428 -- IPv4/IPv6 - cacert (NEW) -- #################################################
429 if has_ssl then
430         cert = ns:taboption("basic", Value, "cacert",
431                 translate("Path to CA-Certificate"),
432                 translate("directory or path/file") .. "<br />" ..
433                 translate("or") .. bold_on .. " IGNORE " .. bold_off ..
434                 translate("to run HTTPS without verification of server certificates (insecure)") )
435         cert:depends("use_https", "1")
436         cert.rmempty = false -- force validate function
437         cert.default = "/etc/ssl/certs"
438         function cert.validate(self, value)
439                 if https:formvalue(section) == "0" then
440                         return ""       -- supress validate error if NOT https
441                 end
442                 if value then   -- otherwise errors in datatype check
443                         if DTYP.directory(value)
444                         or DTYP.file(value)
445                         or value == "IGNORE" then
446                                 return value
447                         end
448                 end
449                 return nil, err_tab_basic(self) ..
450                         translate("file or directory not found or not 'IGNORE'") .. " !"
451         end
452 end
453
454 -- TAB: Advanced  ##################################################################################
455 -- IPv4 - ip_source -- #########################################################
456 src4 = ns:taboption("advanced", ListValue, "ipv4_source",
457         translate("IP address source") .. " [IPv4]",
458         translate("Defines the source to read systems IPv4-Address from, that will be send to the DDNS provider") )
459 src4:depends("use_ipv6", "0")   -- IPv4 selected
460 src4.default = "network"
461 src4:value("network", translate("Network"))
462 src4:value("web", translate("URL"))
463 src4:value("interface", translate("Interface"))
464 src4:value("script", translate("Script"))
465 function src4.cfgvalue(self, section)
466         return DDNS.read_value(self, section, "ip_source")
467 end
468 function src4.validate(self, value)
469         if usev6:formvalue(section) == "1" then
470                 return ""       -- ignore on IPv6 selected
471         elseif not _verify_ip_source() then
472                 return nil, err_tab_adv(self) ..
473                         translate("can not detect local IP. Please select a different Source combination")
474         else
475                 return value
476         end
477 end
478 function src4.write(self, section, value)
479         if usev6:formvalue(section) == "1" then
480                 return true     -- ignore on IPv6 selected
481         elseif value == "network" then
482                 self.map:del(section, "ip_url")         -- delete not need parameters
483                 self.map:del(section, "ip_interface")
484                 self.map:del(section, "ip_script")
485         elseif value == "web" then
486                 self.map:del(section, "ip_network")     -- delete not need parameters
487                 self.map:del(section, "ip_interface")
488                 self.map:del(section, "ip_script")
489         elseif value == "interface" then
490                 self.map:del(section, "ip_network")     -- delete not need parameters
491                 self.map:del(section, "ip_url")
492                 self.map:del(section, "ip_script")
493         elseif value == "script" then
494                 self.map:del(section, "ip_network")
495                 self.map:del(section, "ip_url")         -- delete not need parameters
496                 self.map:del(section, "ip_interface")
497         end
498         self.map:del(section, self.option)               -- delete "ipv4_source" helper
499         return self.map:set(section, "ip_source", value) -- and write "ip_source
500 end
501
502 -- IPv6 - ip_source -- #########################################################
503 src6 = ns:taboption("advanced", ListValue, "ipv6_source",
504         translate("IP address source") .. " [IPv6]",
505         translate("Defines the source to read systems IPv6-Address from, that will be send to the DDNS provider") )
506 src6:depends("use_ipv6", 1)     -- IPv6 selected
507 src6.default = "network"
508 src6:value("network", translate("Network"))
509 src6:value("web", translate("URL"))
510 src6:value("interface", translate("Interface"))
511 src6:value("script", translate("Script"))
512 if not has_ipv6 then
513         src6.description = err_ipv6_other
514 end
515 function src6.cfgvalue(self, section)
516         return DDNS.read_value(self, section, "ip_source")
517 end
518 function src6.validate(self, value)
519         if usev6:formvalue(section) == "0" then
520                 return ""       -- ignore on IPv4 selected
521         elseif not has_ipv6 then
522                 return nil, err_tab_adv(self) .. err_ipv6_plain
523         elseif not _verify_ip_source() then
524                 return nil, err_tab_adv(self) ..
525                         translate("can not detect local IP. Please select a different Source combination")
526         else
527                 return value
528         end
529 end
530 function src6.write(self, section, value)
531         if usev6:formvalue(section) == "0" then
532                 return true     -- ignore on IPv4 selected
533         elseif value == "network" then
534                 self.map:del(section, "ip_url")         -- delete not need parameters
535                 self.map:del(section, "ip_interface")
536                 self.map:del(section, "ip_script")
537         elseif value == "web" then
538                 self.map:del(section, "ip_network")     -- delete not need parameters
539                 self.map:del(section, "ip_interface")
540                 self.map:del(section, "ip_script")
541         elseif value == "interface" then
542                 self.map:del(section, "ip_network")     -- delete not need parameters
543                 self.map:del(section, "ip_url")
544                 self.map:del(section, "ip_script")
545         elseif value == "script" then
546                 self.map:del(section, "ip_network")
547                 self.map:del(section, "ip_url")         -- delete not need parameters
548                 self.map:del(section, "ip_interface")
549         end
550         self.map:del(section, self.option)               -- delete "ipv4_source" helper
551         return self.map:set(section, "ip_source", value) -- and write "ip_source
552 end
553
554 -- IPv4 - ip_network (default "wan") -- ########################################
555 ipn4 = ns:taboption("advanced", ListValue, "ipv4_network",
556         translate("Network") .. " [IPv4]",
557         translate("Defines the network to read systems IPv4-Address from") )
558 ipn4:depends("ipv4_source", "network")
559 ipn4.default = "wan"
560 WADM.cbi_add_networks(ipn4)
561 function ipn4.cfgvalue(self, section)
562         return DDNS.read_value(self, section, "ip_network")
563 end
564 function ipn4.validate(self, value)
565         if usev6:formvalue(section) == "1"
566          or src4:formvalue(section) ~= "network" then
567                 -- ignore if IPv6 selected OR
568                 -- ignore everything except "network"
569                 return ""
570         else
571                 return value
572         end
573 end
574 function ipn4.write(self, section, value)
575         if usev6:formvalue(section) == "1"
576          or src4:formvalue(section) ~= "network" then
577                 -- ignore if IPv6 selected OR
578                 -- ignore everything except "network"
579                 return true
580         else
581                 -- set also as "interface" for monitoring events changes/hot-plug
582                 self.map:set(section, "interface", value)
583                 self.map:del(section, self.option)                -- delete "ipv4_network" helper
584                 return self.map:set(section, "ip_network", value) -- and write "ip_network"
585         end
586 end
587
588 -- IPv6 - ip_network (default "wan6") -- #######################################
589 ipn6 = ns:taboption("advanced", ListValue, "ipv6_network",
590         translate("Network") .. " [IPv6]" )
591 ipn6:depends("ipv6_source", "network")
592 ipn6.default = "wan6"
593 WADM.cbi_add_networks(ipn6)
594 if has_ipv6 then
595         ipn6.description = translate("Defines the network to read systems IPv6-Address from")
596 else
597         ipn6.description = err_ipv6_other
598 end
599 function ipn6.cfgvalue(self, section)
600         return DDNS.read_value(self, section, "ip_network")
601 end
602 function ipn6.validate(self, value)
603         if usev6:formvalue(section) == "0"
604          or src6:formvalue(section) ~= "network" then
605                 -- ignore if IPv4 selected OR
606                 -- ignore everything except "network"
607                 return ""
608         elseif has_ipv6 then
609                 return value
610         else
611                 return nil, err_tab_adv(self) .. err_ipv6_plain
612         end
613 end
614 function ipn6.write(self, section, value)
615         if usev6:formvalue(section) == "0"
616          or src6:formvalue(section) ~= "network" then
617                 -- ignore if IPv4 selected OR
618                 -- ignore everything except "network"
619                 return true
620         else
621                 -- set also as "interface" for monitoring events changes/hotplug
622                 self.map:set(section, "interface", value)
623                 self.map:del(section, self.option)                -- delete "ipv6_network" helper
624                 return self.map:set(section, "ip_network", value) -- and write "ip_network"
625         end
626 end
627
628 -- IPv4 - ip_url (default "checkip.dyndns.com") -- #############################
629 iurl4 = ns:taboption("advanced", Value, "ipv4_url",
630         translate("URL to detect") .. " [IPv4]",
631         translate("Defines the Web page to read systems IPv4-Address from") )
632 iurl4:depends("ipv4_source", "web")
633 iurl4.default = "http://checkip.dyndns.com"
634 function iurl4.cfgvalue(self, section)
635         return DDNS.read_value(self, section, "ip_url")
636 end
637 function iurl4.validate(self, value)
638         if usev6:formvalue(section) == "1"
639          or src4:formvalue(section) ~= "web" then
640                 -- ignore if IPv6 selected OR
641                 -- ignore everything except "web"
642                 return ""
643         elseif not value or #value == 0 then
644                 return nil, err_tab_adv(self) .. translate("missing / required")
645         end
646
647         local url = DDNS.parse_url(value)
648         if not (url.scheme == "http" or url.scheme == "https") then
649                 return nil, err_tab_adv(self) .. translate("must start with 'http://'")
650         elseif not url.host then
651                 return nil, err_tab_adv(self) .. "<HOST> " .. translate("missing / required")
652         elseif SYS.call([[nslookup ]] .. url.host .. [[>/dev/null 2>&1]]) ~= 0 then
653                 return nil, err_tab_adv(self) .. translate("can not resolve host: ") .. url.host
654         else
655                 return value
656         end
657 end
658 function iurl4.write(self, section, value)
659         if usev6:formvalue(section) == "1"
660          or src4:formvalue(section) ~= "web" then
661                 -- ignore if IPv6 selected OR
662                 -- ignore everything except "web"
663                 return true
664         else
665                 self.map:del(section, self.option)              -- delete "ipv4_url" helper
666                 return self.map:set(section, "ip_url", value)   -- and write "ip_url"
667         end
668 end
669
670 -- IPv6 - ip_url (default "checkipv6.dyndns.com") -- ###########################
671 iurl6 = ns:taboption("advanced", Value, "ipv6_url",
672         translate("URL to detect") .. " [IPv6]" )
673 iurl6:depends("ipv6_source", "web")
674 iurl6.default = "http://checkipv6.dyndns.com"
675 if has_ipv6 then
676         iurl6.description = translate("Defines the Web page to read systems IPv6-Address from")
677 else
678         iurl6.description = err_ipv6_other
679 end
680 function iurl6.cfgvalue(self, section)
681         return DDNS.read_value(self, section, "ip_url")
682 end
683 function iurl6.validate(self, value)
684         if usev6:formvalue(section) == "0"
685          or src6:formvalue(section) ~= "web" then
686                 -- ignore if IPv4 selected OR
687                 -- ignore everything except "web"
688                 return ""
689         elseif not has_ipv6 then
690                 return nil, err_tab_adv(self) .. err_ipv6_plain
691         elseif not value or #value == 0 then
692                 return nil, err_tab_adv(self) .. translate("missing / required")
693         end
694
695         local url = DDNS.parse_url(value)
696         if not (url.scheme == "http" or url.scheme == "https") then
697                 return nil, err_tab_adv(self) .. translate("must start with 'http://'")
698         elseif not url.host then
699                 return nil, err_tab_adv(self) .. "<HOST> " .. translate("missing / required")
700         elseif SYS.call([[nslookup ]] .. url.host .. [[>/dev/null 2>&1]]) ~= 0 then
701                 return nil, err_tab_adv(self) .. translate("can not resolve host: ") .. url.host
702         else
703                 return value
704         end
705 end
706 function iurl6.write(self, section, value)
707         if usev6:formvalue(section) == "0"
708          or src6:formvalue(section) ~= "web" then
709                 -- ignore if IPv4 selected OR
710                 -- ignore everything except "web"
711                 return true
712         else
713                 self.map:del(section, self.option)              -- delete "ipv6_url" helper
714                 return self.map:set(section, "ip_url", value)   -- and write "ip_url"
715         end
716 end
717
718 -- IPv4 + IPv6 - ip_interface -- ###############################################
719 ipi = ns:taboption("advanced", ListValue, "ip_interface",
720         translate("Interface"),
721         translate("Defines the interface to read systems IP-Address from") )
722 ipi:depends("ipv4_source", "interface") -- IPv4
723 ipi:depends("ipv6_source", "interface") -- or IPv6
724 for _, v in pairs(SYS.net.devices()) do
725         -- show only interface set to a network
726         -- and ignore loopback
727         net = WADM.iface_get_network(v)
728         if net and net ~= "loopback" then
729                 ipi:value(v)
730         end
731 end
732 function ipi.validate(self, value)
733         if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "interface")
734         or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "interface") then
735                 return ""
736         else
737                 return value
738         end
739 end
740 function ipi.write(self, section, value)
741         if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "interface")
742         or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "interface") then
743                 return true
744         else
745                 -- get network from device to
746                 -- set also as "interface" for monitoring events changes/hotplug
747                 local net = WADM.iface_get_network(value)
748                 self.map:set(section, "interface", net)
749                 return self.map:set(section, self.option, value)
750         end
751 end
752
753 -- IPv4 + IPv6 - ip_script (NEW) -- ############################################
754 ips = ns:taboption("advanced", Value, "ip_script",
755         translate("Script"),
756         translate("User defined script to read systems IP-Address") )
757 ips:depends("ipv4_source", "script")    -- IPv4
758 ips:depends("ipv6_source", "script")    -- or IPv6
759 ips.rmempty     = false
760 ips.placeholder = "/path/to/script.sh"
761 function ips.validate(self, value)
762         local split
763         if value then split = UTIL.split(value, " ") end
764
765         if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "script")
766         or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "script") then
767                 return ""
768         elseif not value or not (#value > 0) or not FS.access(split[1], "x") then
769                 return nil, err_tab_adv(self) ..
770                         translate("not found or not executable - Sample: '/path/to/script.sh'")
771         else
772                 return value
773         end
774 end
775 function ips.write(self, section, value)
776         if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "script")
777         or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "script") then
778                 return true
779         else
780                 return self.map:set(section, self.option, value)
781         end
782 end
783
784 -- IPv4 - interface - default "wan" -- #########################################
785 -- event network to monitor changes/hotplug/dynamic_dns_updater.sh
786 -- only needs to be set if "ip_source"="web" or "script"
787 -- if "ip_source"="network" or "interface" we use their network
788 eif4 = ns:taboption("advanced", ListValue, "ipv4_interface",
789         translate("Event Network") .. " [IPv4]",
790         translate("Network on which the ddns-updater scripts will be started") )
791 eif4:depends("ipv4_source", "web")
792 eif4:depends("ipv4_source", "script")
793 eif4.default = "wan"
794 WADM.cbi_add_networks(eif4)
795 function eif4.cfgvalue(self, section)
796         return DDNS.read_value(self, section, "interface")
797 end
798 function eif4.validate(self, value)
799         if usev6:formvalue(section) == "1"
800          or src4:formvalue(section) == "network"
801          or src4:formvalue(section) == "interface" then
802                 return ""       -- ignore IPv6, network, interface
803         else
804                 return value
805         end
806 end
807 function eif4.write(self, section, value)
808         if usev6:formvalue(section) == "1"
809          or src4:formvalue(section) == "network"
810          or src4:formvalue(section) == "interface" then
811                 return true     -- ignore IPv6, network, interface
812         else
813                 self.map:del(section, self.option)               -- delete "ipv4_interface" helper
814                 return self.map:set(section, "interface", value) -- and write "interface"
815         end
816 end
817
818 -- IPv6 - interface (NEW) - default "wan6" -- ##################################
819 -- event network to monitor changes/hotplug (NEW)
820 -- only needs to be set if "ip_source"="web" or "script"
821 -- if "ip_source"="network" or "interface" we use their network
822 eif6 = ns:taboption("advanced", ListValue, "ipv6_interface",
823         translate("Event Network") .. " [IPv6]" )
824 eif6:depends("ipv6_source", "web")
825 eif6:depends("ipv6_source", "script")
826 eif6.default = "wan6"
827 WADM.cbi_add_networks(eif6)
828 if not has_ipv6 then
829         eif6.description = err_ipv6_other
830 else
831         eif6.description = translate("Network on which the ddns-updater scripts will be started")
832 end
833 function eif6.cfgvalue(self, section)
834         return DDNS.read_value(self, section, "interface")
835 end
836 function eif6.validate(self, value)
837         if usev6:formvalue(section) == "0"
838          or src4:formvalue(section) == "network"
839          or src4:formvalue(section) == "interface" then
840                 return ""       -- ignore IPv4, network, interface
841         elseif not has_ipv6 then
842                 return nil, err_tab_adv(self) .. err_ipv6_plain
843         else
844                 return value
845         end
846 end
847 function eif6.write(self, section, value)
848         if usev6:formvalue(section) == "0"
849          or src4:formvalue(section) == "network"
850          or src4:formvalue(section) == "interface" then
851                 return true     -- ignore IPv4, network, interface
852         else
853                 self.map:del(section, self.option)               -- delete "ipv6_interface" helper
854                 return self.map:set(section, "interface", value) -- and write "interface"
855         end
856 end
857
858 -- IPv4 + IPv6 - force_ipversion (NEW) -- ######################################
859 -- optional to force wget/curl and host to use only selected IP version
860 -- command parameter "-4" or "-6"
861 if has_force or ( ( m:get(section, "force_ipversion") or "0" ) ~= "0" ) then
862         fipv = ns:taboption("advanced", Flag, "force_ipversion",
863                 translate("Force IP Version") )
864         fipv.orientation = "horizontal"
865         function fipv.cfgvalue(self, section)
866                 local value = AbstractValue.cfgvalue(self, section)
867                 if not has_force and value ~= "0" then
868                         self.description = bold_on .. font_red ..
869                                 translate("Force IP Version not supported") .. font_off .. "<br />" ..
870                                 translate("please disable") .. " !" .. bold_off
871                 else
872                         self.description = translate("OPTIONAL: Force the usage of pure IPv4/IPv6 only communication.")
873                 end
874                 return value
875         end
876         function fipv.validate(self, value)
877                 if (value == "1" and has_force) or value == "0" then return value end
878                 return nil, err_tab_adv(self) .. translate("Force IP Version not supported")
879         end
880         function fipv.parse(self, section)
881                 DDNS.flag_parse(self, section)
882         end
883         function fipv.write(self, section, value)
884                 if value == "1" then
885                         return self.map:set(section, self.option, value)
886                 else
887                         return self.map:del(section, self.option)
888                 end
889         end
890 end
891
892 -- IPv4 + IPv6 - dns_server (NEW) -- ###########################################
893 -- optional DNS Server to use resolving my IP if "ip_source"="web"
894 dns = ns:taboption("advanced", Value, "dns_server",
895         translate("DNS-Server"),
896         translate("OPTIONAL: Use non-default DNS-Server to detect 'Registered IP'.") .. "<br />" ..
897         translate("Format: IP or FQDN"))
898 dns.placeholder = "mydns.lan"
899 function dns.validate(self, value)
900         -- if .datatype is set, then it is checked before calling this function
901         if not value then
902                 return ""       -- ignore on empty
903         elseif not DTYP.host(value) then
904                 return nil, err_tab_adv(self) .. translate("use hostname, FQDN, IPv4- or IPv6-Address")
905         else
906                 local ipv6  = usev6:formvalue(section)
907                 local force = (fipv) and fipv:formvalue(section) or "0"
908                 local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh verify_dns ]] ..
909                         value .. [[ ]] .. ipv6 .. [[ ]] .. force
910                 local ret = SYS.call(command)
911                 if     ret == 0 then return value       -- everything OK
912                 elseif ret == 2 then return nil, err_tab_adv(self) .. translate("nslookup can not resolve host")
913                 elseif ret == 3 then return nil, err_tab_adv(self) .. translate("nc (netcat) can not connect")
914                 elseif ret == 4 then return nil, err_tab_adv(self) .. translate("Forced IP Version don't matched")
915                 else                 return nil, err_tab_adv(self) .. translate("unspecific error")
916                 end
917         end
918 end
919
920 -- IPv4 + IPv6 - force_dnstcp (NEW) -- #########################################
921 if has_dnstcp or ( ( m:get(section, "force_dnstcp") or "0" ) ~= "0" ) then
922         tcp = ns:taboption("advanced", Flag, "force_dnstcp",
923                 translate("Force TCP on DNS") )
924         tcp.orientation = "horizontal"
925         function tcp.cfgvalue(self, section)
926                 local value = AbstractValue.cfgvalue(self, section)
927                 if not has_dnstcp and value ~= "0" then
928                         self.description = bold_on .. font_red ..
929                                 translate("DNS requests via TCP not supported") .. font_off .. "<br />" ..
930                                 translate("please disable") .. " !" .. bold_off
931                 else
932                         self.description = translate("OPTIONAL: Force the use of TCP instead of default UDP on DNS requests.")
933                 end
934                 return value
935         end
936         function tcp.validate(self, value)
937                 if (value == "1" and has_dnstcp ) or value == "0" then
938                         return value
939                 end
940                 return nil, err_tab_adv(self) .. translate("DNS requests via TCP not supported")
941         end
942         function tcp.parse(self, section)
943                 DDNS.flag_parse(self, section)
944         end
945 end
946
947 -- IPv4 + IPv6 - proxy (NEW) -- ################################################
948 -- optional Proxy to use for http/https requests  [user:password@]proxyhost[:port]
949 if has_proxy or ( ( m:get(section, "proxy") or "" ) ~= "" ) then
950         pxy = ns:taboption("advanced", Value, "proxy",
951                 translate("PROXY-Server") )
952         pxy.placeholder="user:password@myproxy.lan:8080"
953         function pxy.cfgvalue(self, section)
954                 local value = AbstractValue.cfgvalue(self, section)
955                 if not has_proxy and value ~= "" then
956                         self.description = bold_on .. font_red ..
957                                 translate("PROXY-Server not supported") .. font_off .. "<br />" ..
958                                 translate("please remove entry") .. "!" .. bold_off
959                 else
960                         self.description = translate("OPTIONAL: Proxy-Server for detection and updates.") .. "<br />" ..
961                                 translate("Format") .. ": " .. bold_on .. "[user:password@]proxyhost:port" .. bold_off .. "<br />" ..
962                                 translate("IPv6 address must be given in square brackets") .. ": " ..
963                                 bold_on .. " [2001:db8::1]:8080" .. bold_off
964                 end
965                 return value
966         end
967         function pxy.validate(self, value)
968                 -- if .datatype is set, then it is checked before calling this function
969                 if not value then
970                         return ""       -- ignore on empty
971                 elseif has_proxy then
972                         local ipv6  = usev6:formvalue(section) or "0"
973                         local force = (fipv) and fipv:formvalue(section) or "0"
974                         local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh verify_proxy ]] ..
975                                 value .. [[ ]] .. ipv6 .. [[ ]] .. force
976                         local ret = SYS.call(command)
977                         if     ret == 0 then return value
978                         elseif ret == 2 then return nil, err_tab_adv(self) .. translate("nslookup can not resolve host")
979                         elseif ret == 3 then return nil, err_tab_adv(self) .. translate("nc (netcat) can not connect")
980                         elseif ret == 4 then return nil, err_tab_adv(self) .. translate("Forced IP Version don't matched")
981                         elseif ret == 5 then return nil, err_tab_adv(self) .. translate("proxy port missing")
982                         else                 return nil, err_tab_adv(self) .. translate("unspecific error")
983                         end
984                 else
985                         return nil, err_tab_adv(self) .. translate("PROXY-Server not supported")
986                 end
987         end
988 end
989
990 -- use_syslog -- ###############################################################
991 slog = ns:taboption("advanced", ListValue, "use_syslog",
992         translate("Log to syslog"),
993         translate("Writes log messages to syslog. Critical Errors will always be written to syslog.") )
994 slog.default = "2"
995 slog:value("0", translate("No logging"))
996 slog:value("1", translate("Info"))
997 slog:value("2", translate("Notice"))
998 slog:value("3", translate("Warning"))
999 slog:value("4", translate("Error"))
1000
1001 -- use_logfile (NEW) -- ########################################################
1002 logf = ns:taboption("advanced", Flag, "use_logfile",
1003         translate("Log to file"),
1004         translate("Writes detailed messages to log file. File will be truncated automatically.") .. "<br />" ..
1005         translate("File") .. [[: "]] .. log_dir .. [[/]] .. section .. [[.log"]] )
1006 logf.orientation = "horizontal"
1007 logf.rmempty = false    -- we want to save in /etc/config/ddns file on "0" because
1008 logf.default = "1"      -- if not defined write to log by default
1009 function logf.parse(self, section)
1010         DDNS.flag_parse(self, section)
1011 end
1012
1013 -- TAB: Timer  #####################################################################################
1014 -- check_interval -- ###########################################################
1015 ci = ns:taboption("timer", Value, "check_interval",
1016         translate("Check Interval") )
1017 ci.template = "ddns/detail_value"
1018 ci.default  = 10
1019 ci.rmempty = false      -- validate ourselves for translatable error messages
1020 function ci.validate(self, value)
1021         if not DTYP.uinteger(value)
1022         or tonumber(value) < 1 then
1023                 return nil, err_tab_timer(self) .. translate("minimum value 5 minutes == 300 seconds")
1024         end
1025
1026         local secs = DDNS.calc_seconds(value, cu:formvalue(section))
1027         if secs >= 300 then
1028                 return value
1029         else
1030                 return nil, err_tab_timer(self) .. translate("minimum value 5 minutes == 300 seconds")
1031         end
1032 end
1033 function ci.write(self, section, value)
1034         -- simulate rmempty=true remove default
1035         local secs = DDNS.calc_seconds(value, cu:formvalue(section))
1036         if secs ~= 600 then     --default 10 minutes
1037                 return self.map:set(section, self.option, value)
1038         else
1039                 self.map:del(section, "check_unit")
1040                 return self.map:del(section, self.option)
1041         end
1042 end
1043
1044 -- check_unit -- ###############################################################
1045 cu = ns:taboption("timer", ListValue, "check_unit", "not displayed, but needed otherwise error",
1046         translate("Interval to check for changed IP" .. "<br />" ..
1047                 "Values below 5 minutes == 300 seconds are not supported") )
1048 cu.template = "ddns/detail_lvalue"
1049 cu.default  = "minutes"
1050 cu.rmempty  = false     -- want to control write process
1051 cu:value("seconds", translate("seconds"))
1052 cu:value("minutes", translate("minutes"))
1053 cu:value("hours", translate("hours"))
1054 --cu:value("days", translate("days"))
1055 function cu.write(self, section, value)
1056         -- simulate rmempty=true remove default
1057         local secs = DDNS.calc_seconds(ci:formvalue(section), value)
1058         if secs ~= 600 then     --default 10 minutes
1059                 return self.map:set(section, self.option, value)
1060         else
1061                 return true
1062         end
1063 end
1064
1065 -- force_interval (modified) -- ################################################
1066 fi = ns:taboption("timer", Value, "force_interval",
1067         translate("Force Interval") )
1068 fi.template = "ddns/detail_value"
1069 fi.default  = 72        -- see dynamic_dns_updater.sh script
1070 fi.rmempty = false      -- validate ourselves for translatable error messages
1071 function fi.validate(self, value)
1072         if not DTYP.uinteger(value)
1073         or tonumber(value) < 0 then
1074                 return nil, err_tab_timer(self) .. translate("minimum value '0'")
1075         end
1076
1077         local force_s = DDNS.calc_seconds(value, fu:formvalue(section))
1078         if force_s == 0 then
1079                 return value
1080         end
1081
1082         local ci_value = ci:formvalue(section)
1083         if not DTYP.uinteger(ci_value) then
1084                 return ""       -- ignore because error in check_interval above
1085         end
1086
1087         local check_s = DDNS.calc_seconds(ci_value, cu:formvalue(section))
1088         if force_s >= check_s then
1089                 return value
1090         end
1091
1092         return nil, err_tab_timer(self) .. translate("must be greater or equal 'Check Interval'")
1093 end
1094 function fi.write(self, section, value)
1095         -- simulate rmempty=true remove default
1096         local secs = DDNS.calc_seconds(value, fu:formvalue(section))
1097         if secs ~= 259200 then  --default 72 hours == 3 days
1098                 return self.map:set(section, self.option, value)
1099         else
1100                 self.map:del(section, "force_unit")
1101                 return self.map:del(section, self.option)
1102         end
1103 end
1104
1105 -- force_unit -- ###############################################################
1106 fu = ns:taboption("timer", ListValue, "force_unit", "not displayed, but needed otherwise error",
1107         translate("Interval to force updates send to DDNS Provider" .. "<br />" ..
1108                 "Setting this parameter to 0 will force the script to only run once" .. "<br />" ..
1109                 "Values lower 'Check Interval' except '0' are not supported") )
1110 fu.template = "ddns/detail_lvalue"
1111 fu.default  = "hours"
1112 fu.rmempty  = false     -- want to control write process
1113 --fu:value("seconds", translate("seconds"))
1114 fu:value("minutes", translate("minutes"))
1115 fu:value("hours", translate("hours"))
1116 fu:value("days", translate("days"))
1117 function fu.write(self, section, value)
1118         -- simulate rmempty=true remove default
1119         local secs = DDNS.calc_seconds(fi:formvalue(section), value)
1120         if secs ~= 259200 and secs ~= 0 then    --default 72 hours == 3 days
1121                 return self.map:set(section, self.option, value)
1122         else
1123                 return true
1124         end
1125 end
1126
1127 -- retry_count (NEW) -- ########################################################
1128 rc = ns:taboption("timer", Value, "retry_count",
1129         translate("Error Retry Counter"),
1130         translate("On Error the script will stop execution after given number of retrys") )
1131 rc.default = 5
1132 rc.rmempty = false      -- validate ourselves for translatable error messages
1133 function rc.validate(self, value)
1134         if not DTYP.uinteger(value) then
1135                 return nil, err_tab_timer(self) .. translate("minimum value '0'")
1136         else
1137                 return value
1138         end
1139 end
1140 function rc.write(self, section, value)
1141         -- simulate rmempty=true remove default
1142         if tonumber(value) ~= self.default then
1143                 return self.map:set(section, self.option, value)
1144         else
1145                 return self.map:del(section, self.option)
1146         end
1147 end
1148
1149 -- retry_interval -- ###########################################################
1150 ri = ns:taboption("timer", Value, "retry_interval",
1151         translate("Error Retry Interval") )
1152 ri.template = "ddns/detail_value"
1153 ri.default  = 60
1154 ri.rmempty  = false     -- validate ourselves for translatable error messages
1155 function ri.validate(self, value)
1156         if not DTYP.uinteger(value)
1157         or tonumber(value) < 1 then
1158                 return nil, err_tab_timer(self) .. translate("minimum value '1'")
1159         else
1160                 return value
1161         end
1162 end
1163 function ri.write(self, section, value)
1164         -- simulate rmempty=true remove default
1165         local secs = DDNS.calc_seconds(value, ru:formvalue(section))
1166         if secs ~= 60 then      --default 60seconds
1167                 return self.map:set(section, self.option, value)
1168         else
1169                 self.map:del(section, "retry_unit")
1170                 return self.map:del(section, self.option)
1171         end
1172 end
1173
1174 -- retry_unit -- ###############################################################
1175 ru = ns:taboption("timer", ListValue, "retry_unit", "not displayed, but needed otherwise error",
1176         translate("On Error the script will retry the failed action after given time") )
1177 ru.template = "ddns/detail_lvalue"
1178 ru.default  = "seconds"
1179 ru.rmempty  = false     -- want to control write process
1180 ru:value("seconds", translate("seconds"))
1181 ru:value("minutes", translate("minutes"))
1182 --ru:value("hours", translate("hours"))
1183 --ru:value("days", translate("days"))
1184 function ru.write(self, section, value)
1185         -- simulate rmempty=true remove default
1186         local secs = DDNS.calc_seconds(ri:formvalue(section), value)
1187         if secs ~= 60 then      --default 60seconds
1188                 return self.map:set(section, self.option, value)
1189         else
1190                 return true -- will be deleted by retry_interval
1191         end
1192 end
1193
1194 -- TAB: LogView  (NEW) #############################################################################
1195 lv = ns:taboption("logview", DummyValue, "_logview")
1196 lv.template = "ddns/detail_logview"
1197 lv.inputtitle = translate("Read / Reread log file")
1198 lv.rows = 50
1199 function lv.cfgvalue(self, section)
1200         local lfile=log_dir .. "/" .. section .. ".log"
1201         if FS.access(lfile) then
1202                 return lfile .. "\n" .. translate("Please press [Read] button")
1203         end
1204         return lfile .. "\n" .. translate("File not found or empty")
1205 end
1206
1207 return m