4a234c269ecb12b16308cd49c1ccc7685ab544ef
[project/luci.git] / applications / luci-app-ddns / htdocs / luci-static / resources / view / ddns / overview.js
1 'use strict';
2 'require ui';
3 'require view';
4 'require dom';
5 'require poll';
6 'require uci';
7 'require rpc';
8 'require fs';
9 'require form';
10 'require tools.widgets as widgets';
11
12 return view.extend({
13
14 NextUpdateStrings : {
15 'Verify' : _("Verify"),
16 'Run once' : _("Run once"),
17 'Disabled' : _("Disabled"),
18 'Stopped' : _("Stopped")
19 },
20
21 time_res : {
22 seconds : 1,
23 minutes : 60,
24 hours : 3600,
25 },
26
27 callGetLogServices: rpc.declare({
28 object: 'luci.ddns',
29 method: 'get_services_log',
30 params: [ 'service_name' ],
31 expect: { },
32 }),
33
34 callInitAction: rpc.declare({
35 object: 'luci',
36 method: 'setInitAction',
37 params: [ 'name', 'action' ],
38 expect: { result: false }
39 }),
40
41 callDDnsGetStatus: rpc.declare({
42 object: 'luci.ddns',
43 method: 'get_ddns_state',
44 expect: { }
45 }),
46
47 callDDnsGetEnv: rpc.declare({
48 object: 'luci.ddns',
49 method: 'get_env',
50 expect: { }
51 }),
52
53 callDDnsGetServicesStatus: rpc.declare({
54 object: 'luci.ddns',
55 method: 'get_services_status',
56 expect: { }
57 }),
58
59 services: {},
60
61 /*
62 * Services list is gen by 3 different source:
63 * 1. /usr/share/ddns/default contains the service installed by opkg
64 * 2. /usr/share/ddns/custom contains any service installed by the
65 * user or the ddns script (for example when service are
66 * downloaded)
67 * 3. /usr/share/ddns/list contains all the service that can be
68 * downloaded by using the ddns script ('service on demand' feature)
69 *
70 * (Special services that requires a dedicated package ARE NOT
71 * supported by the 'service on demand' feature)
72 */
73 callGenServiceList: function(m, ev) {
74 return Promise.all([
75 L.resolveDefault(fs.list('/usr/share/ddns/default'), []),
76 L.resolveDefault(fs.list('/usr/share/ddns/custom'), []),
77 L.resolveDefault(fs.read('/usr/share/ddns/list'), null)
78 ]).then(L.bind(function (data) {
79 var default_service = data[0],
80 custom_service = data[1],
81 list_service = data[2] && data[2].split("\n") || [],
82 _this = this;
83
84 this.services = {};
85
86 default_service.forEach(function (service) {
87 _this.services[service.name.replace('.json','')] = true
88 });
89
90 custom_service.forEach(function (service) {
91 _this.services[service.name.replace('.json','')] = true
92 });
93
94 this.services = Object.fromEntries(Object.entries(this.services).sort());
95
96 list_service.forEach(function (service) {
97 if (!_this.services[service])
98 _this.services[service] = false;
99 });
100 }, this))
101 },
102
103 /*
104 * Check if the service is supported.
105 * If the script doesn't find any json assume a 'service on demand' install.
106 * If a json is found check if the ip type is supported.
107 * Invalidate the service_name if is not supported.
108 */
109 handleCheckService : function(s, service_name, ipv6, ev, section_id) {
110
111 var value = service_name.formvalue(section_id);
112 s.service_supported = null;
113 service_name.triggerValidation(section_id);
114
115 return this.handleGetServiceData(value)
116 .then(L.bind(function (service_data) {
117 if (value != '-' && service_data) {
118 service_data = JSON.parse(service_data);
119 if (ipv6.formvalue(section_id) == "1" && !service_data.ipv6) {
120 s.service_supported = false;
121 return;
122 }
123 }
124 s.service_supported = true;
125 }, service_name))
126 .then(L.bind(service_name.triggerValidation, service_name, section_id))
127 },
128
129 handleGetServiceData: function(service) {
130 return Promise.all([
131 L.resolveDefault(fs.read('/usr/share/ddns/custom/'+service+'.json'), null),
132 L.resolveDefault(fs.read('/usr/share/ddns/default/'+service+'.json'), null)
133 ]).then(function(data) {
134 return data[0] || data[1] || null;
135 })
136 },
137
138 handleInstallService: function(m, service_name, section_id, section, _this, ev) {
139 var service = service_name.formvalue(section_id)
140 return fs.exec('/usr/bin/ddns', ['service', 'install', service])
141 .then(L.bind(_this.callGenServiceList, _this))
142 .then(L.bind(m.render, m))
143 .then(L.bind(this.renderMoreOptionsModal, this, section))
144 .catch(function(e) { ui.addNotification(null, E('p', e.message)) });
145 },
146
147 handleRefreshServicesList: function(m, ev) {
148 return fs.exec('/usr/bin/ddns', ['service', 'update'])
149 .then(L.bind(this.load, this))
150 .then(L.bind(this.render, this))
151 .catch(function(e) { ui.addNotification(null, E('p', e.message)) });
152 },
153
154 handleReloadDDnsRule: function(m, section_id, ev) {
155 return fs.exec('/usr/lib/ddns/dynamic_dns_lucihelper.sh',
156 [ '-S', section_id, '--', 'start' ])
157 .then(L.bind(m.load, m))
158 .then(L.bind(m.render, m))
159 .catch(function(e) { ui.addNotification(null, E('p', e.message)) });
160 },
161
162 HandleStopDDnsRule: function(m, section_id, ev) {
163 return fs.exec('/usr/lib/ddns/dynamic_dns_lucihelper.sh',
164 [ '-S', section_id, '--', 'start' ])
165 .then(L.bind(m.render, m))
166 .catch(function(e) { ui.addNotification(null, E('p', e.message)) });
167 },
168
169 handleToggleDDns: function(m, ev) {
170 return this.callInitAction('ddns', 'enabled')
171 .then(L.bind(function (action) { return this.callInitAction('ddns', action ? 'disable' : 'enable')}, this))
172 .then(L.bind(function (action) { return this.callInitAction('ddns', action ? 'stop' : 'start')}, this))
173 .then(L.bind(m.render, m))
174 .catch(function(e) { ui.addNotification(null, E('p', e.message)) });
175 },
176
177 handleRestartDDns: function(m, ev) {
178 return this.callInitAction('ddns', 'restart')
179 .then(L.bind(m.render, m));
180 },
181
182 poll_status: function(map, data) {
183 var status = data[1] || [], service = data[0] || [], rows = map.querySelectorAll('.cbi-section-table-row[data-sid]'),
184 section_id, cfg_detail_ip, cfg_update, cfg_status, host, ip, last_update,
185 next_update, service_status, reload, cfg_enabled, stop,
186 ddns_enabled = map.querySelector('[data-name="_enabled"]').querySelector('.cbi-value-field'),
187 ddns_toggle = map.querySelector('[data-name="_toggle"]').querySelector('button'),
188 services_list = map.querySelector('[data-name="_services_list"]').querySelector('.cbi-value-field');
189
190 ddns_toggle.innerHTML = status['_enabled'] ? _('Stop DDNS') : _('Start DDNS')
191 services_list.innerHTML = status['_services_list'];
192
193 dom.content(ddns_enabled, function() {
194 return E([], [
195 E('div', {}, status['_enabled'] ? _('DDNS Autostart enabled') : [
196 _('DDNS Autostart disabled'),
197 E('div', { 'class' : 'cbi-value-description' },
198 _("Currently DDNS updates are not started at boot or on interface events.") + "<br />" +
199 _("This is the default if you run DDNS scripts by yourself (i.e. via cron with force_interval set to '0')"))
200 ]),]);
201 });
202
203 for (var i = 0; i < rows.length; i++) {
204 section_id = rows[i].getAttribute('data-sid');
205 cfg_detail_ip = rows[i].querySelector('[data-name="_cfg_detail_ip"]');
206 cfg_update = rows[i].querySelector('[data-name="_cfg_update"]');
207 cfg_status = rows[i].querySelector('[data-name="_cfg_status"]');
208 reload = rows[i].querySelector('.cbi-section-actions .reload');
209 stop = rows[i].querySelector('.cbi-section-actions .stop');
210 cfg_enabled = uci.get('ddns', section_id, 'enabled');
211
212 reload.disabled = (status['_enabled'] == 0 || cfg_enabled == 0);
213
214 host = uci.get('ddns', section_id, 'lookup_host') || _('Configuration Error');
215 ip = _('No Data');
216 last_update = _('Never');
217 next_update = _('Unknown');
218 service_status = '<b>' + _('Not Running') + '</b>';
219
220 if (service[section_id]) {
221 stop.disabled = (!service[section_id].pid || (service[section_id].pid && cfg_enabled == '1'));
222 if (service[section_id].ip)
223 ip = service[section_id].ip;
224 if (service[section_id].last_update)
225 last_update = service[section_id].last_update;
226 if (service[section_id].next_update)
227 next_update = this.NextUpdateStrings[service[section_id].next_update] || service[section_id].next_update;
228 if (service[section_id].pid)
229 service_status = '<b>' + _('Running') + '</b> : ' + service[section_id].pid;
230 }
231
232 cfg_detail_ip.innerHTML = host + '<br />' + ip;
233 cfg_update.innerHTML = last_update + '<br />' + next_update;
234 cfg_status.innerHTML = service_status;
235 }
236
237 return;
238 },
239
240 load: function() {
241 return Promise.all([
242 this.callDDnsGetServicesStatus(),
243 this.callDDnsGetStatus(),
244 this.callDDnsGetEnv(),
245 this.callGenServiceList(),
246 uci.load('ddns')
247 ]);
248 },
249
250 render: function(data) {
251 var resolved = data[0] || [];
252 var status = data[1] || [];
253 var env = data[2] || [];
254 var logdir = uci.get('ddns', 'global', 'ddns_logdir') || "/var/log/ddns";
255
256 var _this = this;
257
258 var m, s, o;
259
260 m = new form.Map('ddns', _('Dynamic DNS'));
261
262 s = m.section(form.NamedSection, 'global', 'ddns',);
263
264 s.tab('info', _('Information'));
265 s.tab('global', _('Global Settings'));
266
267 o = s.taboption('info', form.DummyValue, '_version', _('Dynamic DNS Version'));
268 o.cfgvalue = function() {
269 return status[this.option];
270 };
271
272 o = s.taboption('info', form.DummyValue, '_enabled', _('State'));
273 o.cfgvalue = function() {
274 var res = status[this.option];
275 if (!res) {
276 this.description = _("Currently DDNS updates are not started at boot or on interface events.") + "<br />" +
277 _("This is the default if you run DDNS scripts by yourself (i.e. via cron with force_interval set to '0')")
278 }
279 return res ? _('DDNS Autostart enabled') : _('DDNS Autostart disabled')
280 };
281
282 o = s.taboption('info', form.Button, '_toggle');
283 o.title = '&#160;';
284 o.inputtitle = _((status['_enabled'] ? 'stop' : 'start').toUpperCase() + ' DDns');
285 o.inputstyle = 'apply';
286 o.onclick = L.bind(this.handleToggleDDns, this, m);
287
288 o = s.taboption('info', form.Button, '_restart');
289 o.title = '&#160;';
290 o.inputtitle = _('Restart DDns');
291 o.inputstyle = 'apply';
292 o.onclick = L.bind(this.handleRestartDDns, this, m);
293
294 o = s.taboption('info', form.DummyValue, '_services_list', _('Services list last update'));
295 o.cfgvalue = function() {
296 return status[this.option];
297 };
298
299 o = s.taboption('info', form.Button, '_refresh_services');
300 o.title = '&#160;';
301 o.inputtitle = _('Update DDns Services List');
302 o.inputstyle = 'apply';
303 o.onclick = L.bind(this.handleRefreshServicesList, this, m);
304
305 // DDns hints
306
307 if (!env['has_ipv6']) {
308 o = s.taboption('info', form.DummyValue, '_no_ipv6');
309 o.rawhtml = true;
310 o.title = '<b>' + _("IPv6 not supported") + '</b>';
311 o.cfgvalue = function() { return _("IPv6 is currently not (fully) supported by this system") + "<br />" +
312 _("Please follow the instructions on OpenWrt's homepage to enable IPv6 support") + "<br />" +
313 _("or update your system to the latest OpenWrt Release")};
314 }
315
316 if (!env['has_ssl']) {
317 o = s.taboption('info', form.DummyValue, '_no_https');
318 o.titleref = L.url("admin", "system", "opkg")
319 o.rawhtml = true;
320 o.title = '<b>' + _("HTTPS not supported") + '</b>';
321 o.cfgvalue = function() { return _("Neither GNU Wget with SSL nor cURL installed to support secure updates via HTTPS protocol.") +
322 "<br />- " +
323 _("You should install 'wget' or 'curl' or 'uclient-fetch' with 'libustream-*ssl' package.") +
324 "<br />- " +
325 _("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")};
326 }
327
328 if (!env['has_bindnet']) {
329 o = s.taboption('info', form.DummyValue, '_no_bind_network');
330 o.titleref = L.url("admin", "system", "opkg")
331 o.rawhtml = true;
332 o.title = '<b>' + _("Binding to a specific network not supported") + '</b>';
333 o.cfgvalue = function() { return _("Neither GNU Wget with SSL nor cURL installed to select a network to use for communication.") +
334 "<br />- " +
335 _("You should install 'wget' or 'curl' package.") +
336 "<br />- " +
337 _("GNU Wget will use the IP of given network, cURL will use the physical interface.") +
338 "<br />- " +
339 _("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")};
340 }
341
342 if (!env['has_proxy']) {
343 o = s.taboption('info', form.DummyValue, '_no_proxy');
344 o.titleref = L.url("admin", "system", "opkg")
345 o.rawhtml = true;
346 o.title = '<b>' + _("cURL without Proxy Support") + '</b>';
347 o.cfgvalue = function() { return _("cURL is installed, but libcurl was compiled without proxy support.") +
348 "<br />- " +
349 _("You should install 'wget' or 'uclient-fetch' package or replace libcurl.") +
350 "<br />- " +
351 _("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")};
352 }
353
354 if (!env['has_forceip']) {
355 o = s.taboption('info', form.DummyValue, '_no_force_ip');
356 o.titleref = L.url("admin", "system", "opkg")
357 o.rawhtml = true;
358 o.title = '<b>' + _("Force IP Version not supported") + '</b>';
359 o.cfgvalue = function() { return _("BusyBox's nslookup and Wget do not support to specify " +
360 "the IP version to use for communication with DDNS Provider!") +
361 "<br />- " + _("You should install 'wget' or 'curl' or 'uclient-fetch' package.")
362 };
363 }
364
365 if (!env['has_bindhost']) {
366 o = s.taboption('info', form.DummyValue, '_no_dnstcp');
367 o.titleref = L.url("admin", "system", "opkg")
368 o.rawhtml = true;
369 o.title = '<b>' + _("DNS requests via TCP not supported") + '</b>';
370 o.cfgvalue = function() { return _("BusyBox's nslookup and hostip do not support to specify to use TCP " +
371 "instead of default UDP when requesting DNS server!") +
372 "<br />- " +
373 _("You should install 'bind-host' or 'knot-host' or 'drill' package for DNS requests.")};
374 }
375
376 if (!env['has_dnsserver']) {
377 o = s.taboption('info', form.DummyValue, '_no_dnsserver');
378 o.titleref = L.url("admin", "system", "opkg")
379 o.rawhtml = true;
380 o.title = '<b>' + _("Using specific DNS Server not supported") + '</b>';
381 o.cfgvalue = function() { return _("BusyBox's nslookup in the current compiled version " +
382 "does not handle given DNS Servers correctly!") +
383 "<br />- " +
384 _("You should install 'bind-host' or 'knot-host' or 'drill' or 'hostip' package, " +
385 "if you need to specify a DNS server to detect your registered IP.")};
386 }
387
388 if (env['has_ssl'] && !env['has_cacerts']) {
389 o = s.taboption('info', form.DummyValue, '_no_certs');
390 o.titleref = L.url("admin", "system", "opkg")
391 o.rawhtml = true;
392 o.title = '<b>' + _("No certificates found") + '</b>';
393 o.cfgvalue = function() { return _("If using secure communication you should verify server certificates!") +
394 "<br />- " +
395 _("Install 'ca-certificates' package or needed certificates " +
396 "by hand into /etc/ssl/certs default directory")};
397 }
398
399 // Advanced Configuration Section
400
401 o = s.taboption('global', form.Flag, 'upd_privateip', _("Allow non-public IP's"));
402 o.description = _("Non-public and by default blocked IP's") + ':'
403 + '<br /><strong>IPv4: </strong>'
404 + '0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192.168/16'
405 + '<br /><strong>IPv6: </strong>'
406 + '::/32, f000::/4"';
407 o.default = "0";
408 o.optional = true;
409
410 o = s.taboption('global', form.Value, 'ddns_dateformat', _('Date format'));
411 o.description = '<a href="http://www.cplusplus.com/reference/ctime/strftime/" target="_blank">'
412 + _("For supported codes look here")
413 + '</a><br />' +
414 _('Current setting: ') + '<b>' + status['_curr_dateformat'] + '</b>';
415 o.default = "%F %R"
416 o.optional = true;
417 o.rmempty = true;
418
419 o = s.taboption('global', form.Value, 'ddns_rundir', _('Status directory'));
420 o.description = _('Directory contains PID and other status information for each running section.');
421 o.default = "/var/run/ddns";
422 o.optional = true;
423 o.rmempty = true;
424
425 o = s.taboption('global', form.Value, 'ddns_logdir', _('Log directory'));
426 o.description = _('Directory contains Log files for each running section.');
427 o.default = "/var/log/ddns";
428 o.optional = true;
429 o.rmempty = true;
430 o.validate = function(section_id, formvalue) {
431 if (formvalue.indexOf('../') !== -1)
432 return _('"../" not allowed in path for Security Reason.')
433
434 return true;
435 }
436
437 o = s.taboption('global', form.Value, 'ddns_loglines', _('Log length'));
438 o.description = _('Number of last lines stored in log files');
439 o.datatype = 'min(1)';
440 o.default = '250';
441
442 if (env['has_wget'] && env['has_curl']) {
443
444 o = s.taboption('global', form.Flag, 'use_curl', _('Use cURL'));
445 o.description = _('If Wget and cURL package are installed, Wget is used for communication by default.');
446 o.default = "0";
447 o.optional = true;
448 o.rmempty = true;
449
450 }
451
452 o = s.taboption('global', form.Value, 'cacert', _('Ca Certs path'));
453 o.description = _('Ca Certs path that will be used to download services data. Set IGNORE to skip certificate validation.');
454 o.placeholder = 'IGNORE';
455
456 o = s.taboption('global', form.Value, 'services_url', _('Services URL Download'));
457 o.description = _('Url used to download services file. By default is the master openwrt ddns package repo.');
458 o.placeholder = 'https://raw.githubusercontent.com/openwrt/packages/master/net/ddns-scripts/files';
459
460 // DDns services
461 s = m.section(form.GridSection, 'service', _('Services'));
462 s.anonymous = true;
463 s.addremove = true;
464 s.addbtntitle = _('Add new services...');
465
466 s.anonymous = true;
467 s.addremove = true;
468 s.sortable = true;
469
470 s.handleCreateDDnsRule = function(m, name, service_name, ipv6, ev) {
471 var section_id = name.isValid('_new_') ? name.formvalue('_new_') : null,
472 service_value = service_name.isValid('_new_') ? service_name.formvalue('_new_') : null,
473 ipv6_value = ipv6.isValid('_new_') ? ipv6.formvalue('_new_') : null;
474
475 if (section_id == null || section_id == '' || service_value == null || section_id == '' || ipv6_value == null || ipv6_value == '')
476 return;
477
478 return m.save(function() {
479 uci.add('ddns', 'service', section_id);
480 if (service_value != '-') {
481 uci.set('ddns', section_id, 'service_name', service_value);
482 }
483 uci.set('ddns', section_id, 'use_ipv6', ipv6_value);
484 }).then(L.bind(m.children[1].renderMoreOptionsModal, m.children[1], section_id));
485 };
486
487 s.handleAdd = function(ev) {
488 var m2 = new form.Map('ddns'),
489 s2 = m2.section(form.NamedSection, '_new_'),
490 name, ipv6, service_name;
491
492 s2.render = function() {
493 return Promise.all([
494 {},
495 this.renderUCISection('_new_')
496 ]).then(this.renderContents.bind(this));
497 };
498
499 name = s2.option(form.Value, 'name', _('Name'));
500 name.rmempty = false;
501 name.datatype = 'uciname';
502 name.placeholder = _('New DDns Service…');
503 name.validate = function(section_id, value) {
504 if (uci.get('ddns', value) != null)
505 return _('The service name is already used');
506
507 return true;
508 };
509
510 ipv6 = s2.option( form.ListValue, 'use_ipv6',
511 _("IP address version"),
512 _("Defines which IP address 'IPv4/IPv6' is send to the DDNS provider"));
513 ipv6.default = '0';
514 ipv6.value("0", _("IPv4-Address"))
515 if (env["has_ipv6"]) {
516 ipv6.value("1", _("IPv6-Address"))
517 }
518
519 service_name = s2.option(form.ListValue, 'service_name',
520 String.format('%s', _("DDNS Service provider")));
521 service_name.value('-',"-- " + _("custom") + " --");
522 Object.keys(_this.services).sort().forEach(name => service_name.value(name));
523 service_name.validate = function(section_id, value) {
524 if (value == '') return _("Select a service");
525 if (s2.service_supported == null) return _("Checking the service support...");
526 if (!s2.service_supported) return _("Service doesn't support this ip type");
527 return true;
528 };
529
530 ipv6.onchange = L.bind(_this.handleCheckService, _this, s2, service_name, ipv6);
531 service_name.onchange = L.bind(_this.handleCheckService, _this, s2, service_name, ipv6);
532
533 m2.render().then(L.bind(function(nodes) {
534 ui.showModal(_('Add new services...'), [
535 nodes,
536 E('div', { 'class': 'right' }, [
537 E('button', {
538 'class': 'btn',
539 'click': ui.hideModal
540 }, _('Cancel')), ' ',
541 E('button', {
542 'class': 'cbi-button cbi-button-positive important',
543 'click': ui.createHandlerFn(this, 'handleCreateDDnsRule', m, name, service_name, ipv6)
544 }, _('Create service'))
545 ])
546 ], 'cbi-modal');
547
548 nodes.querySelector('[id="%s"] input[type="text"]'.format(name.cbid('_new_'))).focus();
549 }, this));
550 };
551
552 s.renderRowActions = function(section_id) {
553 var tdEl = this.super('renderRowActions', [ section_id, _('Edit') ]),
554 cfg_enabled = uci.get('ddns', section_id, 'enabled'),
555 reload_opt = {
556 'class': 'cbi-button cbi-button-neutral reload',
557 'click': ui.createHandlerFn(_this, 'handleReloadDDnsRule', m, section_id),
558 'title': _('Reload this service'),
559 },
560 stop_opt = {
561 'class': 'cbi-button cbi-button-neutral stop',
562 'click': ui.createHandlerFn(_this, 'HandleStopDDnsRule', m, section_id),
563 'title': _('Stop this service'),
564 };
565
566 if (status['_enabled'] == 0 || cfg_enabled == 0)
567 reload_opt['disabled'] = 'disabled';
568
569 if (!resolved[section_id] || !resolved[section_id].pid ||
570 (resolved[section_id].pid && cfg_enabled == '1'))
571 stop_opt['disabled'] = 'disabled';
572
573 dom.content(tdEl.lastChild, [
574 E('button', stop_opt, _('Stop')),
575 E('button', reload_opt, _('Reload')),
576 tdEl.lastChild.childNodes[0],
577 tdEl.lastChild.childNodes[1],
578 tdEl.lastChild.childNodes[2]
579 ]);
580
581 return tdEl;
582 };
583
584 s.modaltitle = function(section_id) {
585 return _('DDns Service') + ' » ' + section_id;
586 };
587
588 s.addModalOptions = function(s, section_id) {
589
590 var service = uci.get('ddns', section_id, 'service_name') || '-',
591 ipv6 = uci.get('ddns', section_id, 'use_ipv6'), service_name, use_ipv6;
592
593 return _this.handleGetServiceData(service).then(L.bind(function (service_data) {
594 s.service_available = true;
595 s.service_supported = true;
596
597 if (service != '-') {
598 if (!service_data)
599 s.service_available = false;
600 else {
601 service_data = JSON.parse(service_data);
602 if (ipv6 == "1" && !service_data.ipv6)
603 s.service_supported = false;
604 }
605 }
606
607 s.tab('basic', _('Basic Settings'));
608 s.tab('advanced', _('Advanced Settings'));
609 s.tab('timer', _('Timer Settings'));
610 s.tab('logview', _('Log File Viewer'));
611
612 o = s.taboption('basic', form.Flag, 'enabled',
613 _('Enabled'),
614 _("If this service section is disabled it could not be started.")
615 + "<br />" +
616 _("Neither from LuCI interface nor from console."));
617 o.modalonly = true;
618 o.rmempty = false;
619 o.default = '1';
620
621 o = s.taboption('basic', form.Value, 'lookup_host',
622 _("Lookup Hostname"),
623 _("Hostname/FQDN to validate, if IP update happen or necessary"));
624 o.rmempty = false;
625 o.placeholder = "myhost.example.com";
626 o.datatype = 'and(minlength(3),hostname("strict"))';
627 o.modalonly = true;
628
629 use_ipv6 = s.taboption('basic', form.ListValue, 'use_ipv6',
630 _("IP address version"),
631 _("Defines which IP address 'IPv4/IPv6' is send to the DDNS provider"));
632 use_ipv6.default = '0';
633 use_ipv6.modalonly = true;
634 use_ipv6.rmempty = false;
635 use_ipv6.value("0", _("IPv4-Address"))
636 if (env["has_ipv6"]) {
637 use_ipv6.value("1", _("IPv6-Address"))
638 }
639
640 service_name = s.taboption('basic', form.ListValue, 'service_name',
641 String.format('%s', _("DDNS Service provider")));
642 service_name.modalonly = true;
643 service_name.value('-',"-- " + _("custom") + " --");
644 Object.keys(_this.services).sort().forEach(name => service_name.value(name));
645 service_name.cfgvalue = function(section_id) {
646 return uci.get('ddns', section_id, 'service_name') || '-';
647 };
648 service_name.write = function(section_id, service) {
649 if (service != '-') {
650 uci.set('ddns', section_id, 'update_url', null);
651 uci.set('ddns', section_id, 'update_script', null);
652 return uci.set('ddns', section_id, 'service_name', service);
653 }
654 return uci.set('ddns', section_id, 'service_name', null);
655 };
656 service_name.validate = function(section_id, value) {
657 if (value == '') return _("Select a service");
658 if (s.service_available == null) return _("Checking the service support...");
659 if (!s.service_available) return _('Service not installed');
660 if (!s.service_supported) return _("Service doesn't support this ip type");
661 return true;
662 };
663
664 service_name.onchange = L.bind(_this.handleCheckService, _this, s, service_name, use_ipv6);
665 use_ipv6.onchange = L.bind(_this.handleCheckService, _this, s, service_name, use_ipv6);
666
667 if (!s.service_available) {
668 o = s.taboption('basic', form.Button, '_download_service');
669 o.modalonly = true;
670 o.title = _('Service not installed');
671 o.inputtitle = _('Install Service');
672 o.inputstyle = 'apply';
673 o.onclick = L.bind(_this.handleInstallService,
674 this, m, service_name, section_id, s.section, _this)
675 }
676
677 if (!s.service_supported) {
678 o = s.taboption('basic', form.DummyValue, '_not_supported', '&nbsp');
679 o.cfgvalue = function () {
680 return _("Service doesn't support this ip type")
681 };
682 }
683
684 var service_switch = s.taboption('basic', form.Button, '_switch_proto');
685 service_switch.modalonly = true;
686 service_switch.title = _('Really switch service?');
687 service_switch.inputtitle = _('Switch service');
688 service_switch.inputstyle = 'apply';
689 service_switch.onclick = L.bind(function(ev) {
690 if (!s.service_supported) return;
691
692 return s.map.save()
693 .then(L.bind(m.load, m))
694 .then(L.bind(m.render, m))
695 .then(L.bind(this.renderMoreOptionsModal, this, s.section));
696 }, this);
697
698 if (s.service_available && s.service_supported) {
699
700 o = s.taboption('basic', form.Value, 'update_url',
701 _("Custom update-URL"),
702 _("Update URL to be used for updating your DDNS Provider.")
703 + "<br />" +
704 _("Follow instructions you will find on their WEB page."));
705 o.modalonly = true;
706 o.rmempty = true;
707 o.optional = true;
708 o.depends("service_name","-");
709 o.validate = function(section_id, value) {
710 var other = this.section.children.filter(function(o) { return o.option == 'update_script' })[0].formvalue(section_id);
711
712 if ((value == "" && other == "") || (value != "" && other != "")) {
713 return _("Insert a Update Script OR a Update URL");
714 }
715
716 return true;
717 };
718
719 o = s.taboption('basic', form.Value, 'update_script',
720 _("Custom update-script"),
721 _("Custom update script to be used for updating your DDNS Provider."));
722 o.modalonly = true;
723 o.rmempty = true;
724 o.optional = true;
725 o.depends("service_name","-");
726 o.validate = function(section_id, value) {
727 var other = this.section.children.filter(function(o) { return o.option == 'update_url' })[0].formvalue(section_id);
728
729 if ((value == "" && other == "") || (value != "" && other != "")) {
730 return _("Insert a Update Script OR a Update URL");
731 }
732
733 return true;
734 };
735
736 o = s.taboption('basic', form.Value, 'domain',
737 _("Domain"),
738 _("Replaces [DOMAIN] in Update-URL (URL-encoded)"));
739 o.modalonly = true;
740 o.rmempty = false;
741
742 o = s.taboption('basic', form.Value, 'username',
743 _("Username"),
744 _("Replaces [USERNAME] in Update-URL (URL-encoded)"));
745 o.modalonly = true;
746 o.rmempty = false;
747
748 o = s.taboption('basic', form.Value, 'password',
749 _("Password"),
750 _("Replaces [PASSWORD] in Update-URL (URL-encoded)"));
751 o.password = true;
752 o.modalonly = true;
753 o.rmempty = false;
754
755 o = s.taboption('basic', form.Value, 'param_enc',
756 _("Optional Encoded Parameter"),
757 _("Optional: Replaces [PARAMENC] in Update-URL (URL-encoded)"));
758 o.optional = true;
759 o.modalonly = true;
760
761 o = s.taboption('basic', form.Value, 'param_opt',
762 _("Optional Parameter"),
763 _("Optional: Replaces [PARAMOPT] in Update-URL (NOT URL-encoded)"));
764 o.optional = true;
765 o.modalonly = true;
766
767 if (env['has_ssl']) {
768 o = s.taboption('basic', form.Flag, 'use_https',
769 _("Use HTTP Secure"),
770 _("Enable secure communication with DDNS provider"));
771 o.optional = true;
772 o.modalonly = true;
773
774 o = s.taboption('basic', form.Value, 'cacert',
775 _("Path to CA-Certificate"),
776 _("directory or path/file")
777 + "<br />" +
778 _("or")
779 + '<b>' + " IGNORE " + '</b>' +
780 _("to run HTTPS without verification of server certificates (insecure)"));
781 o.modalonly = true;
782 o.depends("use_https", "1");
783 o.placeholder = "/etc/ssl/certs";
784 o.rmempty = false;
785 };
786
787
788 o = s.taboption('advanced', form.ListValue, 'ip_source',
789 _("IP address source"),
790 _("Defines the source to read systems IP-Address from, that will be send to the DDNS provider"));
791 o.modalonly = true;
792 o.default = "network";
793 o.value("network", _("Network"));
794 o.value("web", _("URL"));
795 o.value("interface", _("Interface"));
796 o.value("script", _("Script"));
797 o.write = function(section_id, formvalue) {
798 switch(formvalue) {
799 case 'network':
800 uci.set('ddns', section_id, "ip_url",null);
801 uci.set('ddns', section_id, "ip_interface",null);
802 uci.set('ddns', section_id, "ip_script",null);
803 break;
804 case 'web':
805 uci.set('ddns', section_id, "ip_network",null);
806 uci.set('ddns', section_id, "ip_interface",null);
807 uci.set('ddns', section_id, "ip_script",null);
808 break;
809 case 'interface':
810 uci.set('ddns', section_id, "ip_network",null);
811 uci.set('ddns', section_id, "ip_url",null);
812 uci.set('ddns', section_id, "ip_script",null);
813 break;
814 case 'script':
815 uci.set('ddns', section_id, "ip_network",null);
816 uci.set('ddns', section_id, "ip_url",null);
817 uci.set('ddns', section_id, "ip_interface",null);
818 break;
819 default:
820 break;
821 };
822
823 return uci.set('ddns', section_id, 'ip_source', formvalue )
824 };
825
826 o = s.taboption('advanced', widgets.NetworkSelect, 'ip_network',
827 _("Network"),
828 _("Defines the network to read systems IP-Address from"));
829 o.depends('ip_source','network');
830 o.modalonly = true;
831 o.default = 'wan';
832 o.multiple = false;
833
834 o = s.taboption('advanced', form.Value, 'ip_url',
835 _("URL to detect"),
836 _("Defines the Web page to read systems IP-Address from.")
837 + '<br />' +
838 String.format('%s %s', _('Example for IPv4'), ': http://checkip.dyndns.com')
839 + '<br />' +
840 String.format('%s %s', _('Example for IPv6'), ': http://checkipv6.dyndns.com'));
841 o.depends("ip_source", "web")
842 o.modalonly = true;
843
844 o = s.taboption('advanced', widgets.DeviceSelect, 'ip_interface',
845 _("Interface"),
846 _("Defines the interface to read systems IP-Address from"));
847 o.modalonly = true;
848 o.depends("ip_source", "interface")
849 o.multiple = false;
850 o.default = 'wan';
851
852 o = s.taboption('advanced', form.Value, 'ip_script',
853 _("Script"),
854 _("User defined script to read systems IP-Address"));
855 o.modalonly = true;
856 o.depends("ip_source", "script")
857 o.placeholder = "/path/to/script.sh"
858
859 o = s.taboption('advanced', widgets.DeviceSelect, 'interface',
860 _("Event Network"),
861 _("Network on which the ddns-updater scripts will be started"));
862 o.modalonly = true;
863 o.multiple = false;
864 o.default = 'wan';
865 o.depends("ip_source", "web");
866 o.depends("ip_source", "script");
867
868 o = s.taboption('advanced', form.DummyValue, '_interface',
869 _("Event Network"),
870 _("Network on which the ddns-updater scripts will be started"));
871 o.depends("ip_source", "interface");
872 o.depends("ip_source", "network");
873 o.forcewrite = true;
874 o.modalonly = true;
875 o.cfgvalue = function(section_id) {
876 return uci.get('ddns', section_id, 'interface') || _('This will be autoset to the selected interface');
877 };
878 o.write = function(section_id) {
879 var opt = this.section.children.filter(function(o) { return o.option == 'ip_source' })[0].formvalue(section_id);
880 var val = this.section.children.filter(function(o) { return o.option == 'ip_'+opt })[0].formvalue(section_id);
881 return uci.set('ddns', section_id, 'interface', val);
882 };
883
884 if (env['has_bindnet']) {
885 o = s.taboption('advanced', widgets.NetworkSelect, 'bind_network',
886 _("Bind Network"),
887 _('OPTIONAL: Network to use for communication')
888 + '<br />' +
889 _("Network on which the ddns-updater scripts will be started"));
890 o.depends("ip_source", "web");
891 o.optional = true;
892 o.rmempty = true;
893 o.modalonly = true;
894 }
895
896 if (env['has_forceip']) {
897 o = s.taboption('advanced', form.Flag, 'force_ipversion',
898 _("Force IP Version"),
899 _('OPTIONAL: Force the usage of pure IPv4/IPv6 only communication.'));
900 o.optional = true;
901 o.rmempty = true;
902 o.modalonly = true;
903 }
904
905 if (env['has_dnsserver']) {
906 o = s.taboption("advanced", form.Value, "dns_server",
907 _("DNS-Server"),
908 _("OPTIONAL: Use non-default DNS-Server to detect 'Registered IP'.")
909 + "<br />" +
910 _("Format: IP or FQDN"));
911 o.placeholder = "mydns.lan"
912 o.optional = true;
913 o.rmempty = true;
914 o.modalonly = true;
915 }
916
917 if (env['has_bindhost']) {
918 o = s.taboption("advanced", form.Flag, "force_dnstcp",
919 _("Force TCP on DNS"),
920 _("OPTIONAL: Force the use of TCP instead of default UDP on DNS requests."));
921 o.optional = true;
922 o.rmempty = true;
923 o.modalonly = true;
924 }
925
926 if (env['has_proxy']) {
927 o = s.taboption("advanced", form.Value, "proxy",
928 _("PROXY-Server"),
929 _("OPTIONAL: Proxy-Server for detection and updates.")
930 + "<br />" +
931 String.format('%s: <b>%s</b>', _("Format"), "[user:password@]proxyhost:port")
932 + "<br />" +
933 String.format('%s: <b>%s</b>', _("IPv6 address must be given in square brackets"), "[2001:db8::1]:8080"));
934 o.optional = true;
935 o.rmempty = true;
936 o.modalonly = true;
937 }
938
939 o = s.taboption("advanced", form.ListValue, "use_syslog",
940 _("Log to syslog"),
941 _("Writes log messages to syslog. Critical Errors will always be written to syslog."));
942 o.modalonly = true;
943 o.default = "2"
944 o.optional = true;
945 o.value("0", _("No logging"))
946 o.value("1", _("Info"))
947 o.value("2", _("Notice"))
948 o.value("3", _("Warning"))
949 o.value("4", _("Error"))
950
951 o = s.taboption("advanced", form.Flag, "use_logfile",
952 _("Log to file"));
953 o.default = '1';
954 o.optional = true;
955 o.modalonly = true;
956 o.cfgvalue = function(section_id) {
957 this.description = _("Writes detailed messages to log file. File will be truncated automatically.") + "<br />" +
958 _("File") + ': "' + logdir + '/' + section_id + '.log"';
959 return uci.get('ddns', section_id, 'use_logfile');
960 };
961
962
963 o = s.taboption("timer", form.Value, "check_interval",
964 _("Check Interval"));
965 o.placeholder = "30";
966 o.modalonly = true;
967 o.datatype = 'uinteger';
968 o.validate = function(section_id, formvalue) {
969 var unit = this.section.children.filter(function(o) { return o.option == 'check_unit' })[0].formvalue(section_id),
970 time_to_sec = _this.time_res[unit || 'minutes'] * formvalue;
971
972 if (formvalue && time_to_sec < 300)
973 return _('Values below 5 minutes == 300 seconds are not supported');
974
975 return true;
976 };
977
978 o = s.taboption("timer", form.ListValue, "check_unit",
979 _('Check Unit'),
980 _("Interval unit to check for changed IP"));
981 o.modalonly = true;
982 o.default = "minutes"
983 o.value("seconds", _("seconds"));
984 o.value("minutes", _("minutes"));
985 o.value("hours", _("hours"));
986
987 o = s.taboption("timer", form.Value, "force_interval",
988 _("Force Interval"),
989 _("Interval to force updates send to DDNS Provider")
990 + "<br />" +
991 _("Setting this parameter to 0 will force the script to only run once"));
992 o.placeholder = "72";
993 o.optional = true;
994 o.modalonly = true;
995 o.datatype = 'uinteger';
996 o.validate = function(section_id, formvalue) {
997
998 if (!formvalue)
999 return true;
1000
1001 var check_unit = this.section.children.filter(function(o) { return o.option == 'check_unit' })[0].formvalue(section_id),
1002 check_val = this.section.children.filter(function(o) { return o.option == 'check_interval' })[0].formvalue(section_id),
1003 force_unit = this.section.children.filter(function(o) { return o.option == 'force_unit' })[0].formvalue(section_id),
1004 check_to_sec = _this.time_res[check_unit || 'minutes'] * ( check_val || '30'),
1005 force_to_sec = _this.time_res[force_unit || 'minutes'] * formvalue;
1006
1007 if (force_to_sec != 0 && force_to_sec < check_to_sec)
1008 return _("Values lower 'Check Interval' except '0' are not supported");
1009
1010 return true;
1011 };
1012
1013 o = s.taboption("timer", form.ListValue, "force_unit",
1014 _('Force Unit'),
1015 _("Interval unit to force updates sent to DDNS Provider."));
1016 o.modalonly = true;
1017 o.optional = true;
1018 o.default = "minutes"
1019 o.value("minutes", _("minutes"));
1020 o.value("hours", _("hours"));
1021 o.value("days", _("days"));
1022
1023 o = s.taboption("timer", form.Value, "retry_max_count",
1024 _("Error Max Retry Counter"),
1025 _("On Error the script will stop execution after the given number of retries.")
1026 + "<br />" +
1027 _("The default setting of '0' will retry infinitely."));
1028 o.placeholder = "0";
1029 o.optional = true;
1030 o.modalonly = true;
1031 o.datatype = 'uinteger';
1032
1033 o = s.taboption("timer", form.Value, "retry_interval",
1034 _("Error Retry Interval"),
1035 _("The interval between which each successive retry commences."));
1036 o.placeholder = "60";
1037 o.optional = true;
1038 o.modalonly = true;
1039 o.datatype = 'uinteger';
1040
1041 o = s.taboption("timer", form.ListValue, "retry_unit",
1042 _('Retry Unit'),
1043 _("Which time units to use for retry counters."));
1044 o.modalonly = true;
1045 o.optional = true;
1046 o.default = "seconds"
1047 o.value("seconds", _("seconds"));
1048 o.value("minutes", _("minutes"));
1049
1050 o = s.taboption('logview', form.Button, '_read_log');
1051 o.title = '';
1052 o.depends('use_logfile','1');
1053 o.modalonly = true;
1054 o.inputtitle = _('Read / Reread log file');
1055 o.inputstyle = 'apply';
1056 o.onclick = L.bind(function(ev, section_id) {
1057 return _this.callGetLogServices(section_id).then(L.bind(log_box.update_log, log_box));
1058 }, this);
1059
1060 var log_box = s.taboption("logview", form.DummyValue, "_logview");
1061 log_box.depends('use_logfile','1');
1062 log_box.modalonly = true;
1063
1064 log_box.update_log = L.bind(function(view, log_data) {
1065 return document.getElementById('log_area').textContent = log_data.result;
1066 }, o, this);
1067
1068 log_box.render = L.bind(function() {
1069 return E([
1070 E('p', {}, _('This is the current content of the log file in %h for this service.').format(logdir)),
1071 E('p', {}, E('textarea', { 'style': 'width:100%', 'rows': 20, 'readonly' : 'readonly', 'id' : 'log_area' }, _('Please press [Read] button') ))
1072 ]);
1073 }, o, this);
1074 }
1075
1076 for (var i = 0; i < s.children.length; i++) {
1077 o = s.children[i];
1078 switch (o.option) {
1079 case '_switch_proto':
1080 o.depends({ service_name : service, use_ipv6: ipv6, "!reverse": true })
1081 continue;
1082 case 'enabled':
1083 case 'service_name':
1084 case 'use_ipv6':
1085 case 'update_script':
1086 case 'update_url':
1087 case 'lookup_host':
1088 continue;
1089
1090 default:
1091 if (o.deps.length)
1092 for (var j = 0; j < o.deps.length; j++) {
1093 o.deps[j].service_name = service;
1094 o.deps[j].use_ipv6 = ipv6;
1095 }
1096 else
1097 o.depends({service_name: service, use_ipv6: ipv6 });
1098 }
1099 }
1100 }, this)
1101 )};
1102
1103 o = s.option(form.DummyValue, '_cfg_status', _('Status'));
1104 o.modalonly = false;
1105 o.textvalue = function(section_id) {
1106 var text = '<b>' + _('Not Running') + '</b>';
1107
1108 if (resolved[section_id] && resolved[section_id].pid)
1109 text = '<b>' + _('Running') + '</b> : ' + resolved[section_id].pid;
1110
1111 return text;
1112 };
1113
1114 o = s.option(form.DummyValue, '_cfg_name', _('Name'));
1115 o.modalonly = false;
1116 o.textvalue = function(section_id) {
1117 return '<b>' + section_id + '</b>';
1118 };
1119
1120 o = s.option(form.DummyValue, '_cfg_detail_ip', _('Lookup Hostname') + "<br />" + _('Registered IP'));
1121 o.rawhtml = true;
1122 o.modalonly = false;
1123 o.textvalue = function(section_id) {
1124 var host = uci.get('ddns', section_id, 'lookup_host') || _('Configuration Error'),
1125 ip = _('No Data');
1126 if (resolved[section_id] && resolved[section_id].ip)
1127 ip = resolved[section_id].ip;
1128
1129 return host + '<br />' + ip;
1130 };
1131
1132 o = s.option(form.Flag, 'enabled', _('Enabled'));
1133 o.rmempty = false;
1134 o.editable = true;
1135 o.modalonly = false;
1136
1137 o = s.option(form.DummyValue, '_cfg_update', _('Last Update') + "<br />" + _('Next Update'));
1138 o.rawhtml = true;
1139 o.modalonly = false;
1140 o.textvalue = function(section_id) {
1141 var last_update = _('Never'), next_update = _('Unknown');
1142 if (resolved[section_id]) {
1143 if (resolved[section_id].last_update)
1144 last_update = resolved[section_id].last_update;
1145 if (resolved[section_id].next_update)
1146 next_update = _this.NextUpdateStrings[resolved[section_id].next_update] || resolved[section_id].next_update;
1147 }
1148
1149 return last_update + '<br />' + next_update;
1150 };
1151
1152 return m.render().then(L.bind(function(m, nodes) {
1153 poll.add(L.bind(function() {
1154 return Promise.all([
1155 this.callDDnsGetServicesStatus(),
1156 this.callDDnsGetStatus()
1157 ]).then(L.bind(this.poll_status, this, nodes));
1158 }, this), 5);
1159 return nodes;
1160 }, this, m));
1161 }
1162 });