Merge pull request #3267 from Ansuel/rework-ddns
luci-app-ddns: convert to client side implementation
This commit is contained in:
commit
45240e088e
50 changed files with 14047 additions and 20336 deletions
|
@ -8,30 +8,12 @@
|
|||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
# PKG_NAME:=luci-app-ddns
|
||||
|
||||
# Version == major.minor.patch
|
||||
# increase on new functionality (minor) or patches (patch)
|
||||
PKG_VERSION:=2.4.9
|
||||
|
||||
# Release == build
|
||||
# increase on changes of translation files
|
||||
PKG_RELEASE:=7
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=Ansuel Smith <ansuelsmth@gmail.com>
|
||||
|
||||
# LuCI specific settings
|
||||
LUCI_TITLE:=LuCI Support for Dynamic DNS Client (ddns-scripts)
|
||||
LUCI_DEPENDS:=+luci-compat +luci-lib-ipkg +luci-mod-admin-full +ddns-scripts
|
||||
# LUCI_PKGARCH:=all
|
||||
|
||||
define Package/$(PKG_NAME)/config
|
||||
# shown in make menuconfig <Help>
|
||||
help
|
||||
$(LUCI_TITLE)
|
||||
Version: $(PKG_VERSION)-$(PKG_RELEASE)
|
||||
endef
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
include ../../luci.mk
|
||||
|
||||
|
|
|
@ -0,0 +1,994 @@
|
|||
'use strict';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
'require fs';
|
||||
'require form';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
var callGetLogServices, callInitAction, callDDnsGetStatus;
|
||||
|
||||
var NextUpdateStrings = {};
|
||||
|
||||
NextUpdateStrings = {
|
||||
'Verify' : _("Verify"),
|
||||
'Run once' : _("Run once"),
|
||||
'Disabled' : _("Disabled"),
|
||||
'Stopped' : _("Stopped")
|
||||
}
|
||||
|
||||
var time_res = {};
|
||||
time_res['seconds'] = 1;
|
||||
time_res['minutes'] = 60;
|
||||
time_res['hours'] = 3600;
|
||||
|
||||
callGetLogServices = rpc.declare({
|
||||
object: 'luci.ddns',
|
||||
method: 'get_services_log',
|
||||
params: [ 'service_name' ],
|
||||
expect: { },
|
||||
});
|
||||
|
||||
callInitAction = rpc.declare({
|
||||
object: 'luci',
|
||||
method: 'setInitAction',
|
||||
params: [ 'name', 'action' ],
|
||||
expect: { result: false }
|
||||
});
|
||||
|
||||
callDDnsGetStatus = rpc.declare({
|
||||
object: 'luci.ddns',
|
||||
method: 'get_ddns_state',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
return L.view.extend({
|
||||
|
||||
callDDnsGetEnv: rpc.declare({
|
||||
object: 'luci.ddns',
|
||||
method: 'get_env',
|
||||
expect: { }
|
||||
}),
|
||||
|
||||
callDDnsGetServicesStatus: rpc.declare({
|
||||
object: 'luci.ddns',
|
||||
method: 'get_services_status',
|
||||
expect: { }
|
||||
}),
|
||||
|
||||
poll_status: function(map, data) {
|
||||
var status = data[1] || [], service = data[0] || [], rows = map.querySelectorAll('.cbi-section-table-row[data-sid]'),
|
||||
section_id, cfg_detail_ip, cfg_update, cfg_status, host, ip, last_update,
|
||||
next_update, service_status, reload, cfg_enabled, stop,
|
||||
ddns_enabled = map.querySelector('[data-name="_enabled"]').querySelector('.cbi-value-field'),
|
||||
ddns_toggle = map.querySelector('[data-name="_toggle"]').querySelector('button');
|
||||
|
||||
ddns_toggle.innerHTML = status['_enabled'] ? _('Stop DDNS') : _('Start DDNS')
|
||||
|
||||
L.dom.content(ddns_enabled, function() {
|
||||
return E([], [
|
||||
E('div', {}, status['_enabled'] ? _('DDNS Autostart enabled') : [
|
||||
_('DDNS Autostart disabled'),
|
||||
E('div', { 'class' : 'cbi-value-description' },
|
||||
_("Currently DDNS updates are not started at boot or on interface events.") + "<br>" +
|
||||
_("This is the default if you run DDNS scripts by yourself (i.e. via cron with force_interval set to '0')"))
|
||||
]),]);
|
||||
});
|
||||
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
section_id = rows[i].getAttribute('data-sid');
|
||||
cfg_detail_ip = rows[i].querySelector('[data-name="_cfg_detail_ip"]');
|
||||
cfg_update = rows[i].querySelector('[data-name="_cfg_update"]');
|
||||
cfg_status = rows[i].querySelector('[data-name="_cfg_status"]');
|
||||
reload = rows[i].querySelector('.cbi-section-actions .reload');
|
||||
stop = rows[i].querySelector('.cbi-section-actions .stop');
|
||||
cfg_enabled = uci.get('ddns', section_id, 'enabled');
|
||||
|
||||
reload.disabled = (status['_enabled'] == 0 || cfg_enabled == 0);
|
||||
|
||||
host = uci.get('ddns', section_id, 'lookup_host') || _('Configuration Error');
|
||||
ip = _('No Data');
|
||||
last_update = _('Never');
|
||||
next_update = _('Unknown');
|
||||
service_status = '<b>' + _('Not Running') + '</b>';
|
||||
|
||||
if (service[section_id]) {
|
||||
stop.disabled = (!service[section_id].pid || (service[section_id].pid && cfg_enabled == '1'));
|
||||
if (service[section_id].ip)
|
||||
ip = service[section_id].ip;
|
||||
if (service[section_id].last_update)
|
||||
last_update = service[section_id].last_update;
|
||||
if (service[section_id].next_update)
|
||||
next_update = NextUpdateStrings[service[section_id].next_update] || service[section_id].next_update;
|
||||
if (service[section_id].pid)
|
||||
service_status = '<b>' + _('Running') + '</b> : ' + service[section_id].pid;
|
||||
}
|
||||
|
||||
cfg_detail_ip.innerHTML = host + '<br>' + ip;
|
||||
cfg_update.innerHTML = last_update + '<br>' + next_update;
|
||||
cfg_status.innerHTML = service_status;
|
||||
}
|
||||
|
||||
return;
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
this.callDDnsGetServicesStatus(),
|
||||
callDDnsGetStatus(),
|
||||
this.callDDnsGetEnv(),
|
||||
fs.lines('/etc/ddns/services'),
|
||||
fs.lines('/etc/ddns/services_ipv6'),
|
||||
uci.load('ddns')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var resolved = data[0] || [];
|
||||
var status = data[1] || [];
|
||||
var env = data[2] || [];
|
||||
var logdir = uci.get('ddns', 'global', 'ddns_logdir') || "/var/log/ddns";
|
||||
|
||||
var services4 = [];
|
||||
var services6 = [];
|
||||
|
||||
data[3].forEach(function(item) {
|
||||
if (!item.startsWith("#")) {
|
||||
services4.push(item.split('\t')[0].slice(1,-1));
|
||||
}
|
||||
});
|
||||
|
||||
data[4].forEach(function(item) {
|
||||
if (!item.startsWith("#")) {
|
||||
services6.push(item.split('\t')[0].slice(1,-1));
|
||||
}
|
||||
});
|
||||
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('ddns', _('Dynamic DNS'),);
|
||||
|
||||
var is = m.section(form.NamedSection, 'global', 'ddns', _('Information'));
|
||||
|
||||
s = is;
|
||||
|
||||
o = s.option(form.DummyValue, '_version', _('Dynamic DNS Version'));
|
||||
o.cfgvalue = function() {
|
||||
return status[this.option];
|
||||
};
|
||||
|
||||
o = s.option(form.DummyValue, '_enabled', _('State'));
|
||||
o.cfgvalue = function() {
|
||||
var res = status[this.option];
|
||||
if (!res) {
|
||||
this.description = _("Currently DDNS updates are not started at boot or on interface events.") + "<br>" +
|
||||
_("This is the default if you run DDNS scripts by yourself (i.e. via cron with force_interval set to '0')")
|
||||
}
|
||||
return res ? _('DDNS Autostart enabled') : _('DDNS Autostart disabled')
|
||||
};
|
||||
|
||||
o = s.option(form.DummyValue, '_toggle', ' ');
|
||||
o.cfgvalue = function() {
|
||||
var action = status['_enabled'] ? 'stop' : 'start';
|
||||
return E([], [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-apply',
|
||||
'click': L.ui.createHandlerFn(this, function() {
|
||||
return callDDnsGetStatus().then(L.bind(function(data) {
|
||||
return callInitAction('ddns', action == 'stop' ? 'disable' : 'enable').then(function() {
|
||||
return callInitAction('ddns', action);
|
||||
});
|
||||
}, this)).then(L.bind(m.render, m));
|
||||
})
|
||||
}, _(action.toUpperCase() + ' DDns'))]);
|
||||
};
|
||||
|
||||
o = s.option(form.DummyValue, '_restart', ' ');
|
||||
o.cfgvalue = function() {
|
||||
return E([], [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-apply',
|
||||
'click': L.ui.createHandlerFn(this, function() {
|
||||
return callInitAction('ddns', 'restart').then(L.bind(m.render, m));
|
||||
})
|
||||
}, _('Restart DDns'))]);
|
||||
};
|
||||
|
||||
// DDns hints
|
||||
|
||||
if (!env['has_ipv6']) {
|
||||
o = s.option(form.DummyValue, '_no_ipv6');
|
||||
o.rawhtml = true;
|
||||
o.title = '<b>' + _("IPv6 not supported") + '</b>';
|
||||
o.cfgvalue = function() { return _("IPv6 is currently not (fully) supported by this system") + "<br>" +
|
||||
_("Please follow the instructions on OpenWrt's homepage to enable IPv6 support") + "<br>" +
|
||||
_("or update your system to the latest OpenWrt Release")};
|
||||
}
|
||||
|
||||
if (!env['has_ssl']) {
|
||||
o = s.option(form.DummyValue, '_no_https');
|
||||
o.titleref = L.url("admin", "system", "opkg")
|
||||
o.rawhtml = true;
|
||||
o.title = '<b>' + _("HTTPS not supported") + '</b>';
|
||||
o.cfgvalue = function() { return _("Neither GNU Wget with SSL nor cURL installed to support secure updates via HTTPS protocol.") +
|
||||
"<br />- " +
|
||||
_("You should install 'wget' or 'curl' or 'uclient-fetch' with 'libustream-*ssl' package.") +
|
||||
"<br />- " +
|
||||
_("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")};
|
||||
}
|
||||
|
||||
if (!env['has_bindnet']) {
|
||||
o = s.option(form.DummyValue, '_no_bind_network');
|
||||
o.titleref = L.url("admin", "system", "opkg")
|
||||
o.rawhtml = true;
|
||||
o.title = '<b>' + _("Binding to a specific network not supported") + '</b>';
|
||||
o.cfgvalue = function() { return _("Neither GNU Wget with SSL nor cURL installed to select a network to use for communication.") +
|
||||
"<br />- " +
|
||||
_("You should install 'wget' or 'curl' package.") +
|
||||
"<br />- " +
|
||||
_("GNU Wget will use the IP of given network, cURL will use the physical interface.") +
|
||||
"<br />- " +
|
||||
_("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")};
|
||||
}
|
||||
|
||||
if (!env['has_proxy']) {
|
||||
o = s.option(form.DummyValue, '_no_proxy');
|
||||
o.titleref = L.url("admin", "system", "opkg")
|
||||
o.rawhtml = true;
|
||||
o.title = '<b>' + _("cURL without Proxy Support") + '</b>';
|
||||
o.cfgvalue = function() { return _("cURL is installed, but libcurl was compiled without proxy support.") +
|
||||
"<br />- " +
|
||||
_("You should install 'wget' or 'uclient-fetch' package or replace libcurl.") +
|
||||
"<br />- " +
|
||||
_("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")};
|
||||
}
|
||||
|
||||
if (!env['has_forceip']) {
|
||||
o = s.option(form.DummyValue, '_no_force_ip');
|
||||
o.titleref = L.url("admin", "system", "opkg")
|
||||
o.rawhtml = true;
|
||||
o.title = '<b>' + _("Force IP Version not supported") + '</b>';
|
||||
o.cfgvalue = function() { return _("BusyBox's nslookup and Wget do not support to specify " +
|
||||
"the IP version to use for communication with DDNS Provider!") +
|
||||
"<br />- " + _("You should install 'wget' or 'curl' or 'uclient-fetch' package.")
|
||||
};
|
||||
}
|
||||
|
||||
if (!env['has_bindhost']) {
|
||||
o = s.option(form.DummyValue, '_no_dnstcp');
|
||||
o.titleref = L.url("admin", "system", "opkg")
|
||||
o.rawhtml = true;
|
||||
o.title = '<b>' + _("DNS requests via TCP not supported") + '</b>';
|
||||
o.cfgvalue = function() { return _("BusyBox's nslookup and hostip do not support to specify to use TCP " +
|
||||
"instead of default UDP when requesting DNS server!") +
|
||||
"<br />- " +
|
||||
_("You should install 'bind-host' or 'knot-host' or 'drill' package for DNS requests.")};
|
||||
}
|
||||
|
||||
if (!env['has_dnsserver']) {
|
||||
o = s.option(form.DummyValue, '_no_dnsserver');
|
||||
o.titleref = L.url("admin", "system", "opkg")
|
||||
o.rawhtml = true;
|
||||
o.title = '<b>' + _("Using specific DNS Server not supported") + '</b>';
|
||||
o.cfgvalue = function() { return _("BusyBox's nslookup in the current compiled version " +
|
||||
"does not handle given DNS Servers correctly!") +
|
||||
"<br />- " +
|
||||
_("You should install 'bind-host' or 'knot-host' or 'drill' or 'hostip' package, " +
|
||||
"if you need to specify a DNS server to detect your registered IP.")};
|
||||
}
|
||||
|
||||
if (env['has_ssl'] && !env['has_cacerts']) {
|
||||
o = s.option(form.DummyValue, '_no_certs');
|
||||
o.titleref = L.url("admin", "system", "opkg")
|
||||
o.rawhtml = true;
|
||||
o.title = '<b>' + _("No certificates found") + '</b>';
|
||||
o.cfgvalue = function() { return _("If using secure communication you should verify server certificates!") +
|
||||
"<br />- " +
|
||||
_("Install 'ca-certificates' package or needed certificates " +
|
||||
"by hand into /etc/ssl/certs default directory")};
|
||||
}
|
||||
|
||||
// DDns services
|
||||
s = m.section(form.GridSection, 'service', _('Services'));
|
||||
s.anonymous = true;
|
||||
s.addremove = true;
|
||||
s.addbtntitle = _('Add new services...');
|
||||
|
||||
s.anonymous = true;
|
||||
s.addremove = true;
|
||||
s.sortable = true;
|
||||
|
||||
s.handleAdd = function(ev) {
|
||||
var m2 = new form.Map('ddns'),
|
||||
s2 = m2.section(form.NamedSection, '_new_');
|
||||
|
||||
s2.render = function() {
|
||||
return Promise.all([
|
||||
{},
|
||||
this.renderUCISection('_new_')
|
||||
]).then(this.renderContents.bind(this));
|
||||
};
|
||||
|
||||
name = s2.option(form.Value, 'name', _('Name'));
|
||||
name.rmempty = false;
|
||||
name.datatype = 'uciname';
|
||||
name.placeholder = _('New DDns Service…');
|
||||
name.validate = function(section_id, value) {
|
||||
if (uci.get('ddns', value) != null)
|
||||
return _('The service name is already used');
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
m2.render().then(L.bind(function(nodes) {
|
||||
L.ui.showModal(_('Add new services...'), [
|
||||
nodes,
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': L.ui.hideModal
|
||||
}, _('Cancel')), ' ',
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-positive important',
|
||||
'click': L.ui.createHandlerFn(this, function(ev) {
|
||||
var nameval = name.isValid('_new_') ? name.formvalue('_new_') : null;
|
||||
|
||||
if (nameval == null || nameval == '')
|
||||
return;
|
||||
|
||||
return m.save(function() {
|
||||
uci.add('ddns', 'service', nameval);
|
||||
}).then(L.bind(m.children[1].renderMoreOptionsModal, m.children[1], nameval));
|
||||
})
|
||||
}, _('Create service'))
|
||||
])
|
||||
], 'cbi-modal');
|
||||
|
||||
nodes.querySelector('[id="%s"] input[type="text"]'.format(name.cbid('_new_'))).focus();
|
||||
}, this));
|
||||
};
|
||||
|
||||
s.renderRowActions = function(section_id) {
|
||||
var tdEl = this.super('renderRowActions', [ section_id, _('Edit') ]),
|
||||
cfg_enabled = uci.get('ddns', section_id, 'enabled'),
|
||||
reload_opt = {
|
||||
'class': 'cbi-button cbi-button-neutral reload',
|
||||
'click': L.ui.createHandlerFn(this, function() {
|
||||
return fs.exec('/usr/lib/ddns/dynamic_dns_lucihelper.sh',
|
||||
[ '-S', section_id, '--', 'start' ]).then(L.bind(m.render, m));
|
||||
}),
|
||||
'title': _('Reload this service'),
|
||||
},
|
||||
stop_opt = {
|
||||
'class': 'cbi-button cbi-button-neutral stop',
|
||||
'click': L.ui.createHandlerFn(this, function() {
|
||||
return fs.exec('/usr/lib/ddns/dynamic_dns_lucihelper.sh',
|
||||
[ '-S', section_id, '--', 'start' ]).then(L.bind(m.render, m));
|
||||
}),
|
||||
'title': _('Stop this service'),
|
||||
};
|
||||
|
||||
if (status['_enabled'] == 0 || cfg_enabled == 0)
|
||||
reload_opt['disabled'] = 'disabled';
|
||||
|
||||
if (!resolved[section_id] || !resolved[section_id].pid ||
|
||||
(resolved[section_id].pid && cfg_enabled == '1'))
|
||||
stop_opt['disabled'] = 'disabled';
|
||||
|
||||
L.dom.content(tdEl.lastChild, [
|
||||
E('button', stop_opt, _('Stop')),
|
||||
E('button', reload_opt, _('Reload')),
|
||||
tdEl.lastChild.childNodes[0],
|
||||
tdEl.lastChild.childNodes[1],
|
||||
tdEl.lastChild.childNodes[2]
|
||||
]);
|
||||
|
||||
return tdEl;
|
||||
};
|
||||
|
||||
o = s.option(form.DummyValue, '_cfg_name', _('Name'));
|
||||
o.modalonly = false;
|
||||
o.textvalue = function(section_id) {
|
||||
return '<b>' + section_id + '</b>';
|
||||
}
|
||||
|
||||
o = s.option(form.DummyValue, '_cfg_detail_ip', _('Lookup Hostname') + "<br>" + _('Registered IP'));
|
||||
o.rawhtml = true;
|
||||
o.modalonly = false;
|
||||
o.textvalue = function(section_id) {
|
||||
var host = uci.get('ddns', section_id, 'lookup_host') || _('Configuration Error'),
|
||||
ip = _('No Data');
|
||||
if (resolved[section_id] && resolved[section_id].ip)
|
||||
ip = resolved[section_id].ip;
|
||||
|
||||
return host + '<br>' + ip;
|
||||
};
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enabled'));
|
||||
o.rmempty = false;
|
||||
o.editable = true;
|
||||
o.modalonly = false;
|
||||
|
||||
o = s.option(form.DummyValue, '_cfg_update', _('Last Update') + "<br>" + _('Next Update'));
|
||||
o.rawhtml = true;
|
||||
o.modalonly = false;
|
||||
o.textvalue = function(section_id) {
|
||||
var last_update = _('Never'), next_update = _('Unknown');
|
||||
if (resolved[section_id]) {
|
||||
if (resolved[section_id].last_update)
|
||||
last_update = resolved[section_id].last_update;
|
||||
if (resolved[section_id].next_update)
|
||||
next_update = NextUpdateStrings[resolved[section_id].next_update] || resolved[section_id].next_update;
|
||||
}
|
||||
|
||||
return last_update + '<br>' + next_update;
|
||||
};
|
||||
|
||||
s.modaltitle = function(section_id) {
|
||||
return _('DDns Service') + ' » ' + section_id;
|
||||
};
|
||||
|
||||
o = s.option(form.DummyValue, '_cfg_status', _('Status'));
|
||||
o.modalonly = false;
|
||||
o.textvalue = function(section_id) {
|
||||
var text = '<b>' + _('Not Running') + '</b>';
|
||||
|
||||
if (resolved[section_id] && resolved[section_id].pid)
|
||||
text = '<b>' + _('Running') + '</b> : ' + resolved[section_id].pid;
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
s.tab('basic', _('Basic Settings'));
|
||||
s.tab('advanced', _('Advanced Settings'));
|
||||
s.tab('timer', _('Timer Settings'));
|
||||
s.tab('logview', _('Log File Viewer'));
|
||||
|
||||
// TAB: BASIC
|
||||
|
||||
// enabled
|
||||
o = s.taboption('basic', form.Flag, 'enabled', _('Enabled'),_("If this service section is disabled it could not be started." + "<br />" +
|
||||
"Neither from LuCI interface nor from console"));
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
o.default = '1';
|
||||
|
||||
// lookup_host
|
||||
|
||||
o = s.taboption('basic', form.Value, 'lookup_host', _("Lookup Hostname"),
|
||||
_("Hostname/FQDN to validate, if IP update happen or necessary") );
|
||||
o.rmempty = false;
|
||||
o.placeholder = "myhost.example.com";
|
||||
o.datatype = 'and(minlength(3),hostname("strict"))';
|
||||
o.modalonly = true;
|
||||
|
||||
// use_ipv6
|
||||
|
||||
o = s.taboption('basic', form.ListValue, 'use_ipv6', _("IP address version"),
|
||||
_("Defines which IP address 'IPv4/IPv6' is send to the DDNS provider"));
|
||||
o.default = '0';
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
|
||||
o.value("0", _("IPv4-Address"))
|
||||
if (env["has_ipv6"])
|
||||
o.value("1", _("IPv6-Address"))
|
||||
|
||||
// service_name
|
||||
|
||||
o = s.taboption('basic', form.ListValue, 'ipv4_service_name', _("DDNS Service provider") + " [IPv4]");
|
||||
o.depends("use_ipv6", "0")
|
||||
o.modalonly = true;
|
||||
|
||||
for (var i = 0; i < services4.length; i++)
|
||||
o.value(services4[i]);
|
||||
|
||||
o.value('-',"-- " + _("custom") + " --");
|
||||
|
||||
o.cfgvalue = function(section_id) {
|
||||
return uci.get('ddns', section_id, 'service_name');
|
||||
}
|
||||
|
||||
o.write = function(section_id, formvalue) {
|
||||
if (formvalue != '-') {
|
||||
uci.set('ddns', section_id, 'update_url', null);
|
||||
uci.set('ddns', section_id, 'update_script', null);
|
||||
return uci.set('ddns', section_id, 'service_name', formvalue);
|
||||
}
|
||||
return uci.set('ddns', section_id, 'service_name', null);
|
||||
};
|
||||
|
||||
o = s.taboption('basic', form.ListValue, 'ipv6_service_name', _("DDNS Service provider") + " [IPv6]");
|
||||
o.depends("use_ipv6", "1")
|
||||
o.modalonly = true;
|
||||
|
||||
for (var i = 0; i < services6.length; i++)
|
||||
o.value(services6[i]);
|
||||
|
||||
o.value('-',"-- " + _("custom") + " --");
|
||||
|
||||
o.cfgvalue = function(section_id) {
|
||||
return uci.get('ddns', section_id, 'service_name');
|
||||
}
|
||||
|
||||
o.write = function(section_id, formvalue) {
|
||||
if (formvalue != '-') {
|
||||
uci.set('ddns', section_id, 'update_url', null);
|
||||
uci.set('ddns', section_id, 'update_script', null);
|
||||
return uci.set('ddns', section_id, 'service_name', formvalue);
|
||||
}
|
||||
return uci.set('ddns', section_id, 'service_name', null);
|
||||
};
|
||||
|
||||
// update_url
|
||||
|
||||
o = s.taboption('basic', form.Value, 'update_url', _("Custom update-URL"),
|
||||
_("Update URL to be used for updating your DDNS Provider." + "<br />" +
|
||||
"Follow instructions you will find on their WEB page."));
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
o.depends("ipv6_service_name","-");
|
||||
o.depends("ipv4_service_name","-");
|
||||
|
||||
// update_script
|
||||
|
||||
o = s.taboption('basic', form.Value, 'update_script', _("Custom update-script"),
|
||||
_("Custom update script to be used for updating your DDNS Provider."));
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
o.depends("ipv6_service_name","-");
|
||||
o.depends("ipv4_service_name","-");
|
||||
|
||||
// domain
|
||||
|
||||
o = s.taboption('basic', form.Value, 'domain', _("Domain"),
|
||||
_("Replaces [USERNAME] in Update-URL (URL-encoded)"));
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
|
||||
// username
|
||||
|
||||
o = s.taboption('basic', form.Value, 'username', _("Username"),
|
||||
_("Replaces [USERNAME] in Update-URL (URL-encoded)"));
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
|
||||
// password
|
||||
|
||||
|
||||
o = s.taboption('basic', form.Value, 'password', _("Password"),
|
||||
_("Replaces [PASSWORD] in Update-URL (URL-encoded)"));
|
||||
o.password = true;
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
|
||||
// param_enc
|
||||
|
||||
o = s.taboption('basic', form.Value, 'param_enc', _("Optional Encoded Parameter"),
|
||||
_("Optional: Replaces [PARAMENC] in Update-URL (URL-encoded)"));
|
||||
o.optional = true;
|
||||
o.modalonly = true;
|
||||
|
||||
// param_opt
|
||||
|
||||
o = s.taboption('basic', form.Value, 'param_opt', _("Optional Parameter"),
|
||||
_("Optional: Replaces [PARAMOPT] in Update-URL (NOT URL-encoded)"));
|
||||
o.optional = true;
|
||||
o.modalonly = true;
|
||||
|
||||
// use_https
|
||||
|
||||
if (env['has_ssl']) {
|
||||
o = s.taboption('basic', form.Flag, 'use_https', _("Use HTTP Secure"),
|
||||
_("Enable secure communication with DDNS provider"));
|
||||
o.optional = true;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('basic', form.Value, 'cacert',
|
||||
_("Path to CA-Certificate"),
|
||||
_("directory or path/file") + "<br>" +
|
||||
_("or") + '<b>' + " IGNORE " + '</b>' +
|
||||
_("to run HTTPS without verification of server certificates (insecure)"));
|
||||
o.modalonly = true;
|
||||
o.depends("use_https", "1");
|
||||
o.placeholder = "/etc/ssl/certs";
|
||||
o.rmempty = false;
|
||||
};
|
||||
|
||||
// TAB Advanced
|
||||
|
||||
// ip_source
|
||||
|
||||
o = s.taboption('advanced', form.ListValue, 'ip_source', _("IP address source"),
|
||||
_("Defines the source to read systems IP-Address from, that will be send to the DDNS provider"));
|
||||
|
||||
o.modalonly = true;
|
||||
o.default = "network";
|
||||
o.value("network", _("Network"));
|
||||
o.value("web", _("URL"));
|
||||
o.value("interface", _("Interface"));
|
||||
o.value("script", _("Script"));
|
||||
|
||||
o.write = function(section_id, formvalue) {
|
||||
switch(formvalue) {
|
||||
case 'network':
|
||||
uci.set('ddns', section_id, "ip_url",null);
|
||||
uci.set('ddns', section_id, "ip_interface",null);
|
||||
uci.set('ddns', section_id, "ip_script",null);
|
||||
break;
|
||||
case 'web':
|
||||
uci.set('ddns', section_id, "ip_network",null);
|
||||
uci.set('ddns', section_id, "ip_interface",null);
|
||||
uci.set('ddns', section_id, "ip_script",null);
|
||||
break;
|
||||
case 'interface':
|
||||
uci.set('ddns', section_id, "ip_network",null);
|
||||
uci.set('ddns', section_id, "ip_url",null);
|
||||
uci.set('ddns', section_id, "ip_script",null);
|
||||
break;
|
||||
case 'script':
|
||||
uci.set('ddns', section_id, "ip_network",null);
|
||||
uci.set('ddns', section_id, "ip_url",null);
|
||||
uci.set('ddns', section_id, "ip_interface",null);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
return uci.set('ddns', section_id, 'ip_source', formvalue )
|
||||
};
|
||||
|
||||
// ip_network
|
||||
|
||||
o = s.taboption('advanced', widgets.ZoneSelect, 'ip_network', _("Network"),
|
||||
_("Defines the network to read systems IP-Address from"));
|
||||
o.depends('ip_source','network');
|
||||
o.modalonly = true;
|
||||
o.default = 'wan';
|
||||
o.multiple = false;
|
||||
|
||||
// ip_url
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'ip_url', _("URL to detect"),
|
||||
_("Defines the Web page to read systems IP-Address from" + '<br>' +
|
||||
_('Example for IPv4' + ': http://checkip.dyndns.com') + '<br>' +
|
||||
_('Example for IPv6' + ': http://checkipv6.dyndns.com')));
|
||||
o.depends("ip_source", "web")
|
||||
|
||||
o.modalonly = true;
|
||||
|
||||
// ip_interface
|
||||
|
||||
o = s.taboption('advanced', widgets.ZoneSelect, 'ip_interface', _("Interface"),
|
||||
_("Defines the interface to read systems IP-Address from"));
|
||||
|
||||
o.modalonly = true;
|
||||
o.depends("ip_source", "interface")
|
||||
o.multiple = false;
|
||||
o.default = 'wan';
|
||||
|
||||
// ip_script
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'ip_script', _("Script"),
|
||||
_("User defined script to read systems IP-Address"));
|
||||
|
||||
o.modalonly = true;
|
||||
o.depends("ip_source", "script")
|
||||
o.placeholder = "/path/to/script.sh"
|
||||
|
||||
// interface
|
||||
|
||||
o = s.taboption('advanced', widgets.ZoneSelect, 'interface', _("Event Network"),
|
||||
_("Network on which the ddns-updater scripts will be started"));
|
||||
|
||||
o.modalonly = true;
|
||||
o.multiple = false;
|
||||
o.default = 'wan';
|
||||
o.depends("ip_source", "web");
|
||||
o.depends("ip_source", "script");
|
||||
|
||||
// interface_show
|
||||
|
||||
o = s.taboption('advanced', form.DummyValue, '_interface', _("Event Network"),
|
||||
_("Network on which the ddns-updater scripts will be started"));
|
||||
o.depends("ip_source", "interface");
|
||||
o.depends("ip_source", "network");
|
||||
o.forcewrite = true;
|
||||
o.modalonly = true;
|
||||
o.cfgvalue = function(section_id) {
|
||||
return uci.get('ddns', section_id, 'interface') || _('This will be autoset to the selected interface');
|
||||
};
|
||||
o.write = function(section_id) {
|
||||
var opt = this.section.children.filter(function(o) { return o.option == 'ip_source' })[0].formvalue(section_id);
|
||||
var val = this.section.children.filter(function(o) { return o.option == 'ip_'+opt })[0].formvalue(section_id);
|
||||
return uci.set('ddns', section_id, 'interface', val);
|
||||
};
|
||||
|
||||
// bind_network
|
||||
|
||||
if (env['has_bindnet']) {
|
||||
o = s.taboption('advanced', widgets.ZoneSelect, 'bind_network', _("Bind Network"),
|
||||
_('OPTIONAL: Network to use for communication') + '<br>' +
|
||||
_("Network on which the ddns-updater scripts will be started"));
|
||||
o.depends("ip_source", "web");
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
}
|
||||
|
||||
// force_ipversion
|
||||
|
||||
if (env['has_forceip']) {
|
||||
o = s.taboption('advanced', form.Flag, 'force_ipversion', _("Force IP Version"),
|
||||
_('OPTIONAL: Force the usage of pure IPv4/IPv6 only communication.'));
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
}
|
||||
|
||||
// dns_server
|
||||
|
||||
if (env['has_dnsserver']) {
|
||||
o = s.taboption("advanced", form.Value, "dns_server",
|
||||
_("DNS-Server"),
|
||||
_("OPTIONAL: Use non-default DNS-Server to detect 'Registered IP'.") + "<br />" +
|
||||
_("Format: IP or FQDN"));
|
||||
o.placeholder = "mydns.lan"
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
}
|
||||
|
||||
// force_dnstcp
|
||||
|
||||
if (env['has_bindhost']) {
|
||||
o = s.taboption("advanced", form.Flag, "force_dnstcp",
|
||||
_("Force TCP on DNS"),
|
||||
_("OPTIONAL: Force the use of TCP instead of default UDP on DNS requests."));
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
}
|
||||
|
||||
// proxy
|
||||
|
||||
if (env['has_proxy']) {
|
||||
o = s.taboption("advanced", form.Value, "proxy", _("PROXY-Server"),
|
||||
_("OPTIONAL: Proxy-Server for detection and updates.") + "<br />" +
|
||||
_("Format") + ": " + '<b>' + "[user:password@]proxyhost:port" + '</b>' + "<br />" +
|
||||
_("IPv6 address must be given in square brackets") + ": " +
|
||||
'<b>' + " [2001:db8::1]:8080" + '</b>');
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
}
|
||||
|
||||
// use_syslog
|
||||
|
||||
o = s.taboption("advanced", form.ListValue, "use_syslog", _("Log to syslog"),
|
||||
_("Writes log messages to syslog. Critical Errors will always be written to syslog."));
|
||||
o.modalonly = true;
|
||||
o.placeholder = "2"
|
||||
o.optional = true;
|
||||
o.value("0", _("No logging"))
|
||||
o.value("1", _("Info"))
|
||||
o.value("2", _("Notice"))
|
||||
o.value("3", _("Warning"))
|
||||
o.value("4", _("Error"))
|
||||
|
||||
// use_logfile
|
||||
|
||||
o = s.taboption("advanced", form.Flag, "use_logfile", _("Log to file"));
|
||||
o.default = '1';
|
||||
o.optional = true;
|
||||
o.modalonly = true;
|
||||
o.cfgvalue = function(section_id) {
|
||||
this.description = _("Writes detailed messages to log file. File will be truncated automatically.") + "<br>" +
|
||||
_("File") + ': "' + logdir + '/' + section_id + '.log"';
|
||||
return uci.get('ddns', section_id, 'use_logfile');
|
||||
};
|
||||
|
||||
// TAB Timer
|
||||
|
||||
// check_interval
|
||||
o = s.taboption("timer", form.Value, "check_interval", _("Check Interval"));
|
||||
o.placeholder = "30";
|
||||
o.modalonly = true;
|
||||
o.datatype = 'uinteger';
|
||||
|
||||
o.validate = function(section_id, formvalue) {
|
||||
var unit = this.section.children.filter(function(o) { return o.option == 'check_unit' })[0].formvalue(section_id),
|
||||
time_to_sec = time_res[unit || 'minutes'] * formvalue;
|
||||
|
||||
if (formvalue && time_to_sec < 300)
|
||||
return _('Values below 5 minutes == 300 seconds are not supported');
|
||||
|
||||
return true;
|
||||
|
||||
};
|
||||
|
||||
// check_interval
|
||||
o = s.taboption("timer", form.ListValue, "check_unit",'Check Unit');
|
||||
o.description = _("Interval unit to check for changed IP");
|
||||
o.modalonly = true;
|
||||
o.default = "minutes"
|
||||
o.value("seconds", _("seconds"));
|
||||
o.value("minutes", _("minutes"));
|
||||
o.value("hours", _("hours"));
|
||||
|
||||
// force_interval
|
||||
|
||||
o = s.taboption("timer", form.Value, "force_interval", _("Force Interval"));
|
||||
o.description = _("Interval to force updates send to DDNS Provider" + "<br />" +
|
||||
"Setting this parameter to 0 will force the script to only run once");
|
||||
o.placeholder = "72";
|
||||
o.optional = true;
|
||||
o.modalonly = true;
|
||||
o.datatype = 'uinteger';
|
||||
|
||||
o.validate = function(section_id, formvalue) {
|
||||
|
||||
if (!formvalue)
|
||||
return true;
|
||||
|
||||
var check_unit = this.section.children.filter(function(o) { return o.option == 'check_unit' })[0].formvalue(section_id),
|
||||
check_val = this.section.children.filter(function(o) { return o.option == 'check_interval' })[0].formvalue(section_id),
|
||||
force_unit = this.section.children.filter(function(o) { return o.option == 'force_unit' })[0].formvalue(section_id),
|
||||
check_to_sec = time_res[check_unit || 'minutes'] * ( check_val || '30'),
|
||||
force_to_sec = time_res[force_unit || 'minutes'] * formvalue;
|
||||
|
||||
if (force_to_sec != 0 && force_to_sec < check_to_sec)
|
||||
return _("Values lower 'Check Interval' except '0' are not supported");
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// force_unit
|
||||
|
||||
o = s.taboption("timer", form.ListValue, "force_unit",'Force Unit');
|
||||
o.description = _("Interval unit to force updates send to DDNS Provider");
|
||||
o.modalonly = true;
|
||||
o.optional = true;
|
||||
o.default = "minutes"
|
||||
o.value("minutes", _("minutes"));
|
||||
o.value("hours", _("hours"));
|
||||
o.value("hours", _("days"));
|
||||
|
||||
// retry_count
|
||||
|
||||
o = s.taboption("timer", form.Value, "retry_count", _("Error Retry Counter"));
|
||||
o.description = _("On Error the script will stop execution after given number of retrys")
|
||||
+ "<br />"
|
||||
+ _("The default setting of '0' will retry infinite.");
|
||||
o.placeholder = "0";
|
||||
o.optional = true;
|
||||
o.modalonly = true;
|
||||
o.datatype = 'uinteger';
|
||||
|
||||
// retry_interval
|
||||
|
||||
o = s.taboption("timer", form.Value, "retry_interval", _("Error Retry Interval"));
|
||||
o.description = _("On Error the script will stop execution after given number of retrys")
|
||||
+ "<br />"
|
||||
+ _("The default setting of '0' will retry infinite.");
|
||||
o.placeholder = "60";
|
||||
o.optional = true;
|
||||
o.modalonly = true;
|
||||
o.datatype = 'uinteger';
|
||||
|
||||
// retry_unit
|
||||
|
||||
o = s.taboption("timer", form.ListValue, "retry_unit",'Retry Unit');
|
||||
o.description = _("On Error the script will retry the failed action after given time");
|
||||
o.modalonly = true;
|
||||
o.optional = true;
|
||||
o.default = "seconds"
|
||||
o.value("seconds", _("seconds"));
|
||||
o.value("minutes", _("minutes"));
|
||||
|
||||
// TAB logview
|
||||
|
||||
o = s.taboption("logview", form.DummyValue, '_read_log', '');
|
||||
o.depends('use_logfile','1');
|
||||
o.modalonly = true;
|
||||
o.cfgvalue = function(section_id) {
|
||||
return E([], [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-apply',
|
||||
'click': L.ui.createHandlerFn(this, function() {
|
||||
var o = this.section.children.filter(function(o) { return o.option == '_logview' })[0];
|
||||
return callGetLogServices(section_id).then(L.bind(o.update_log, o));
|
||||
})
|
||||
}, _('Read / Reread log file'))]);
|
||||
};
|
||||
|
||||
o = s.taboption("logview", form.DummyValue, "_logview");
|
||||
o.depends('use_logfile','1');
|
||||
o.modalonly = true;
|
||||
|
||||
o.update_log = L.bind(function(view, log_data) {
|
||||
return document.getElementById('log_area').innerHTML = log_data.result;
|
||||
}, o, this)
|
||||
|
||||
o.render = L.bind(function() {
|
||||
return E([
|
||||
E('p', {}, _('This is the current content of the log file in ') + logdir + ' for this service.'),
|
||||
E('p', {}, E('textarea', { 'style': 'width:100%', 'rows': 20, 'readonly' : 'readonly', 'id' : 'log_area' }, _('Please press [Read] button') ))
|
||||
]);
|
||||
}, o, this)
|
||||
|
||||
|
||||
// Advanced Configuration Section
|
||||
|
||||
s = m.section(form.NamedSection, 'global', 'ddns', _('Global Configuration'));
|
||||
s.description = _('Configure here the details for all Dynamic DNS services including this LuCI application.')
|
||||
+ '<br /><strong>'
|
||||
+ _("It is NOT recommended for casual users to change settings on this page.")
|
||||
+ '</strong><br />'
|
||||
+ '<a href="https://openwrt.org/docs/guide-user/base-system/ddns#section_ddns" target="_blank">'
|
||||
+ _('For detailed information about parameter settings look here.')
|
||||
+ '</a>';
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'upd_privateip', _("Allow non-public IP's"));
|
||||
o.description = _("Non-public and by default blocked IP's") + ':'
|
||||
+ '<br /><strong>IPv4: </strong>'
|
||||
+ '0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192.168/16'
|
||||
+ '<br /><strong>IPv6: </strong>'
|
||||
+ '::/32, f000::/4"';
|
||||
o.default = "0";
|
||||
o.optional = true;
|
||||
|
||||
o = s.option(form.Value, 'ddns_dateformat', _('Date format'));
|
||||
o.description = '<a href="http://www.cplusplus.com/reference/ctime/strftime/" target="_blank">'
|
||||
+ _("For supported codes look here")
|
||||
+ '</a><br>' +
|
||||
_('Current setting: ') + '<b>' + status['_curr_dateformat'] + '</b>';
|
||||
o.default = "%F %R"
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.option(form.Value, 'ddns_rundir', _('Status directory'));
|
||||
o.description = _('Directory contains PID and other status information for each running section.');
|
||||
o.default = "/var/run/ddns";
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.option(form.Value, 'ddns_logdir', _('Log directory'));
|
||||
o.description = _('Directory contains Log files for each running section.');
|
||||
o.default = "/var/log/ddns";
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
o.validate = function(section_id, formvalue) {
|
||||
if (formvalue.indexOf('../') !== -1)
|
||||
return _('"../" not allowed in path for Security Reason.')
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
o = s.option(form.Value, 'ddns_loglines', _('Log length'));
|
||||
o.description = _('Number of last lines stored in log files');
|
||||
o.datatype = 'min(1)';
|
||||
o.default = '250';
|
||||
|
||||
if (env['has_wget'] && env['has_curl']) {
|
||||
|
||||
o = s.option(form.Flag, 'use_curl', _('Use cURL'));
|
||||
o.description = _('If Wget and cURL package are installed, Wget is used for communication by default.');
|
||||
o.default = "0";
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
|
||||
}
|
||||
|
||||
return m.render().then(L.bind(function(m, nodes) {
|
||||
L.Poll.add(L.bind(function() {
|
||||
return Promise.all([
|
||||
this.callDDnsGetServicesStatus(),
|
||||
callDDnsGetStatus()
|
||||
]).then(L.bind(this.poll_status, this, nodes));
|
||||
}, this), 5);
|
||||
return nodes;
|
||||
}, this, m));
|
||||
}
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
'use strict';
|
||||
'require rpc';
|
||||
'require uci';
|
||||
|
||||
return L.Class.extend({
|
||||
title: _('Dynamic DNS'),
|
||||
|
||||
callDDnsGetServicesStatus: rpc.declare({
|
||||
object: 'luci.ddns',
|
||||
method: 'get_services_status',
|
||||
expect: { }
|
||||
}),
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
this.callDDnsGetServicesStatus(),
|
||||
uci.load('ddns')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var services = data[0];
|
||||
|
||||
var table = E('div', { 'class': 'table' }, [
|
||||
E('div', { 'class': 'tr table-titles' }, [
|
||||
E('div', { 'class': 'th' }, _('Configuration')),
|
||||
E('div', { 'class': 'th' }, _('Next Update')),
|
||||
E('div', { 'class': 'th' }, _('Lookup Hostname')),
|
||||
E('div', { 'class': 'th' }, _('Registered IP')),
|
||||
E('div', { 'class': 'th' }, _('Network'))
|
||||
])
|
||||
]);
|
||||
|
||||
cbi_update_table(table, Object.keys(services).map(function(key, index) {
|
||||
return [
|
||||
key,
|
||||
services[key].next_update ? _(services[key].next_update) : _('Unknown'),
|
||||
uci.get('ddns',key,'lookup_host'),
|
||||
services[key].ip ? services[key].ip : _('No Data'),
|
||||
(uci.get('ddns',key,'use_ipv6') == '1' ? 'IPv6' : 'IPv4') + ' / ' + uci.get('ddns',key,'interface')
|
||||
];
|
||||
}), E('em', _('There is no service configured.')));
|
||||
|
||||
return E([table]);
|
||||
}
|
||||
});
|
|
@ -6,334 +6,6 @@
|
|||
|
||||
module("luci.controller.ddns", package.seeall)
|
||||
|
||||
local NX = require "nixio"
|
||||
local NXFS = require "nixio.fs"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local HTTP = require "luci.http"
|
||||
local I18N = require "luci.i18n" -- not globally avalible here
|
||||
local IPKG = require "luci.model.ipkg"
|
||||
local SYS = require "luci.sys"
|
||||
local UCI = require "luci.model.uci"
|
||||
local UTIL = require "luci.util"
|
||||
local DDNS = require "luci.tools.ddns" -- ddns multiused functions
|
||||
|
||||
luci_helper = "/usr/lib/ddns/dynamic_dns_lucihelper.sh"
|
||||
|
||||
local srv_name = "ddns-scripts"
|
||||
local srv_ver_min = "2.7.7" -- minimum version of service required
|
||||
local app_name = "luci-app-ddns"
|
||||
local app_title = "Dynamic DNS"
|
||||
local app_version = "2.4.9-1"
|
||||
|
||||
local translate = I18N.translate
|
||||
|
||||
function index()
|
||||
local nxfs = require "nixio.fs" -- global definitions not available
|
||||
local sys = require "luci.sys" -- in function index()
|
||||
local muci = require "luci.model.uci"
|
||||
|
||||
-- no config create an empty one
|
||||
if not nxfs.access("/etc/config/ddns") then
|
||||
nxfs.writefile("/etc/config/ddns", "")
|
||||
entry( {"admin", "services", "ddns"}, view("ddns/overview"), _("Dynamic DNS"), 59)
|
||||
end
|
||||
|
||||
-- preset new option "lookup_host" if not already defined
|
||||
local uci = muci.cursor()
|
||||
local commit = false
|
||||
uci:foreach("ddns", "service", function (s)
|
||||
if not s["lookup_host"] and s["domain"] then
|
||||
uci:set("ddns", s[".name"], "lookup_host", s["domain"])
|
||||
commit = true
|
||||
end
|
||||
end)
|
||||
if commit then uci:commit("ddns") end
|
||||
uci:unload("ddns")
|
||||
|
||||
entry( {"admin", "services", "ddns"}, cbi("ddns/overview"), _("Dynamic DNS"), 59)
|
||||
entry( {"admin", "services", "ddns", "detail"}, cbi("ddns/detail"), nil ).leaf = true
|
||||
entry( {"admin", "services", "ddns", "hints"}, cbi("ddns/hints",
|
||||
{hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), nil ).leaf = true
|
||||
entry( {"admin", "services", "ddns", "global"}, cbi("ddns/global"), nil ).leaf = true
|
||||
entry( {"admin", "services", "ddns", "logview"}, call("logread") ).leaf = true
|
||||
entry( {"admin", "services", "ddns", "startstop"}, post("startstop") ).leaf = true
|
||||
entry( {"admin", "services", "ddns", "status"}, call("status") ).leaf = true
|
||||
end
|
||||
|
||||
-- Application specific information functions
|
||||
function app_description()
|
||||
local tmp = {}
|
||||
tmp[#tmp+1] = translate("Dynamic DNS allows that your router can be reached with \
|
||||
a fixed hostname while having a dynamically changing IP address.")
|
||||
tmp[#tmp+1] = [[<br />]]
|
||||
tmp[#tmp+1] = translate("OpenWrt Wiki") .. ": "
|
||||
tmp[#tmp+1] = [[<a href="https://openwrt.org/docs/guide-user/services/ddns/client" target="_blank">]]
|
||||
tmp[#tmp+1] = translate("DDNS Client Documentation")
|
||||
tmp[#tmp+1] = [[</a>]]
|
||||
tmp[#tmp+1] = " --- "
|
||||
tmp[#tmp+1] = [[<a href="https://openwrt.org/docs/guide-user/base-system/ddns" target="_blank">]]
|
||||
tmp[#tmp+1] = translate("DDNS Client Configuration")
|
||||
tmp[#tmp+1] = [[</a>]]
|
||||
|
||||
return table.concat(tmp)
|
||||
end
|
||||
function app_title_back()
|
||||
local tmp = {}
|
||||
tmp[#tmp+1] = [[<a href="]]
|
||||
tmp[#tmp+1] = DISP.build_url("admin", "services", "ddns")
|
||||
tmp[#tmp+1] = [[">]]
|
||||
tmp[#tmp+1] = translate(app_title)
|
||||
tmp[#tmp+1] = [[</a>]]
|
||||
return table.concat(tmp)
|
||||
end
|
||||
|
||||
-- Standardized application/service functions
|
||||
function app_title_main()
|
||||
local tmp = {}
|
||||
tmp[#tmp+1] = [[<a href="javascript:alert(']]
|
||||
tmp[#tmp+1] = translate("Version Information")
|
||||
tmp[#tmp+1] = [[\n\n]] .. app_name
|
||||
tmp[#tmp+1] = [[\n]] .. translate("Version") .. [[: ]] .. app_version
|
||||
tmp[#tmp+1] = [[\n\n]] .. srv_name .. [[ ]] .. translate("required") .. [[:]]
|
||||
tmp[#tmp+1] = [[\n]] .. translate("Version") .. [[: ]]
|
||||
tmp[#tmp+1] = srv_ver_min .. [[ ]] .. translate("or higher")
|
||||
tmp[#tmp+1] = [[\n\n]] .. srv_name .. [[ ]] .. translate("installed") .. [[:]]
|
||||
tmp[#tmp+1] = [[\n]] .. translate("Version") .. [[: ]]
|
||||
tmp[#tmp+1] = (service_version() or translate("NOT installed"))
|
||||
tmp[#tmp+1] = [[\n\n]]
|
||||
tmp[#tmp+1] = [[')">]]
|
||||
tmp[#tmp+1] = translate(app_title)
|
||||
tmp[#tmp+1] = [[</a>]]
|
||||
|
||||
return table.concat(tmp)
|
||||
end
|
||||
|
||||
function service_version()
|
||||
|
||||
local srv_ver_cmd = luci_helper .. " -V | awk {'print $2'} "
|
||||
local ver
|
||||
|
||||
if IPKG then
|
||||
ver = IPKG.info(srv_name)[srv_name].Version
|
||||
else
|
||||
ver = UTIL.exec(srv_ver_cmd)
|
||||
end
|
||||
|
||||
if ver and #ver > 0 then return ver or nil end
|
||||
|
||||
end
|
||||
|
||||
function service_ok()
|
||||
return IPKG.compare_versions((service_version() or "0"), ">=", srv_ver_min)
|
||||
end
|
||||
|
||||
-- internal function to read all sections status and return data array
|
||||
local function _get_status()
|
||||
local uci = UCI.cursor()
|
||||
local service = SYS.init.enabled("ddns") and 1 or 0
|
||||
local url_start = DISP.build_url("admin", "system", "startup")
|
||||
local data = {} -- Array to transfer data to javascript
|
||||
|
||||
data[#data+1] = {
|
||||
enabled = service, -- service enabled
|
||||
url_up = url_start, -- link to enable DDS (System-Startup)
|
||||
}
|
||||
|
||||
uci:foreach("ddns", "service", function (s)
|
||||
|
||||
-- Get section we are looking at
|
||||
-- and enabled state
|
||||
local section = s[".name"]
|
||||
local enabled = tonumber(s["enabled"]) or 0
|
||||
local datelast = "_empty_" -- formatted date of last update
|
||||
local datenext = "_empty_" -- formatted date of next update
|
||||
local datenextstat = nil
|
||||
|
||||
-- get force seconds
|
||||
local force_seconds = DDNS.calc_seconds(
|
||||
tonumber(s["force_interval"]) or 72 ,
|
||||
s["force_unit"] or "hours" )
|
||||
-- get/validate pid and last update
|
||||
local pid = DDNS.get_pid(section)
|
||||
local uptime = SYS.uptime()
|
||||
local lasttime = DDNS.get_lastupd(section)
|
||||
if lasttime > uptime then -- /var might not be linked to /tmp
|
||||
lasttime = 0 -- and/or not cleared on reboot
|
||||
end
|
||||
|
||||
-- no last update happen
|
||||
if lasttime == 0 then
|
||||
datelast = "_never_"
|
||||
|
||||
-- we read last update
|
||||
else
|
||||
-- calc last update
|
||||
-- sys.epoch - sys uptime + lastupdate(uptime)
|
||||
local epoch = os.time() - uptime + lasttime
|
||||
-- use linux date to convert epoch
|
||||
datelast = DDNS.epoch2date(epoch)
|
||||
-- calc and fill next update
|
||||
datenext = DDNS.epoch2date(epoch + force_seconds)
|
||||
end
|
||||
|
||||
-- process running but update needs to happen
|
||||
-- problems if force_seconds > uptime
|
||||
force_seconds = (force_seconds > uptime) and uptime or force_seconds
|
||||
if pid > 0 and ( lasttime + force_seconds - uptime ) <= 0 then
|
||||
datenext = "_verify_"
|
||||
datenextstat = translate("Verify")
|
||||
|
||||
-- run once
|
||||
elseif force_seconds == 0 then
|
||||
datenext = "_runonce_"
|
||||
datenextstat = translate("Run once")
|
||||
|
||||
-- no process running and NOT enabled
|
||||
elseif pid == 0 and enabled == 0 then
|
||||
datenext = "_disabled_"
|
||||
datenextstat = translate("Disabled")
|
||||
|
||||
-- no process running and enabled
|
||||
elseif pid == 0 and enabled ~= 0 then
|
||||
datenext = "_stopped_"
|
||||
datenextstat = translate("Stopped")
|
||||
end
|
||||
|
||||
-- get/set monitored interface and IP version
|
||||
local iface = s["interface"] or "wan"
|
||||
local use_ipv6 = tonumber(s["use_ipv6"]) or 0
|
||||
local ipv = (use_ipv6 == 1) and "IPv6" or "IPv4"
|
||||
iface = ipv .. " / " .. iface
|
||||
|
||||
-- try to get registered IP
|
||||
local lookup_host = s["lookup_host"] or "_nolookup_"
|
||||
|
||||
local chk_sec = DDNS.calc_seconds(
|
||||
tonumber(s["check_interval"]) or 10,
|
||||
s["check_unit"] or "minutes" )
|
||||
local reg_ip = DDNS.get_regip(section, chk_sec)
|
||||
|
||||
if reg_ip == "NOFILE" then
|
||||
local dnsserver = s["dns_server"] or ""
|
||||
local force_ipversion = tonumber(s["force_ipversion"] or 0)
|
||||
local force_dnstcp = tonumber(s["force_dnstcp"] or 0)
|
||||
local is_glue = tonumber(s["is_glue"] or 0)
|
||||
local command = luci_helper .. [[ -]]
|
||||
if (use_ipv6 == 1) then command = command .. [[6]] end
|
||||
if (force_ipversion == 1) then command = command .. [[f]] end
|
||||
if (force_dnstcp == 1) then command = command .. [[t]] end
|
||||
if (is_glue == 1) then command = command .. [[g]] end
|
||||
command = command .. [[l ]] .. lookup_host
|
||||
command = command .. [[ -S ]] .. section
|
||||
if (#dnsserver > 0) then command = command .. [[ -d ]] .. dnsserver end
|
||||
command = command .. [[ -- get_registered_ip]]
|
||||
reg_ip = SYS.exec(command)
|
||||
end
|
||||
|
||||
-- fill transfer array
|
||||
data[#data+1] = {
|
||||
section = section,
|
||||
enabled = enabled,
|
||||
iface = iface,
|
||||
lookup = lookup_host,
|
||||
reg_ip = reg_ip,
|
||||
pid = pid,
|
||||
datelast = datelast,
|
||||
datenext = datenext,
|
||||
datenextstat = datenextstat
|
||||
}
|
||||
end)
|
||||
|
||||
uci:unload("ddns")
|
||||
return data
|
||||
end
|
||||
|
||||
-- called by XHR.get from detail_logview.htm
|
||||
function logread(section)
|
||||
-- read application settings
|
||||
local uci = UCI.cursor()
|
||||
local ldir = uci:get("ddns", "global", "ddns_logdir") or "/var/log/ddns"
|
||||
local lfile = ldir .. "/" .. section .. ".log"
|
||||
local ldata = NXFS.readfile(lfile)
|
||||
|
||||
if not ldata or #ldata == 0 then
|
||||
ldata="_nodata_"
|
||||
end
|
||||
uci:unload("ddns")
|
||||
HTTP.write(ldata)
|
||||
end
|
||||
|
||||
-- called by XHR.get from overview_status.htm
|
||||
function startstop(section, enabled)
|
||||
local uci = UCI.cursor()
|
||||
local pid = DDNS.get_pid(section)
|
||||
local data = {} -- Array to transfer data to javascript
|
||||
|
||||
-- if process running we want to stop and return
|
||||
if pid > 0 then
|
||||
local tmp = NX.kill(pid, 15) -- terminate
|
||||
NX.nanosleep(2) -- 2 second "show time"
|
||||
-- status changed so return full status
|
||||
data = _get_status()
|
||||
HTTP.prepare_content("application/json")
|
||||
HTTP.write_json(data)
|
||||
return
|
||||
end
|
||||
|
||||
-- read uncommitted changes
|
||||
-- we don't save and commit data from other section or other options
|
||||
-- only enabled will be done
|
||||
local exec = true
|
||||
local changed = uci:changes("ddns")
|
||||
for k_config, v_section in pairs(changed) do
|
||||
-- security check because uci.changes only gets our config
|
||||
if k_config ~= "ddns" then
|
||||
exec = false
|
||||
break
|
||||
end
|
||||
for k_section, v_option in pairs(v_section) do
|
||||
-- check if only section of button was changed
|
||||
if k_section ~= section then
|
||||
exec = false
|
||||
break
|
||||
end
|
||||
for k_option, v_value in pairs(v_option) do
|
||||
-- check if only enabled was changed
|
||||
if k_option ~= "enabled" then
|
||||
exec = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- we can not execute because other
|
||||
-- uncommitted changes pending, so exit here
|
||||
if not exec then
|
||||
HTTP.write("_uncommitted_")
|
||||
return
|
||||
end
|
||||
|
||||
-- save enable state
|
||||
uci:set("ddns", section, "enabled", ( (enabled == "true") and "1" or "0") )
|
||||
uci:save("ddns")
|
||||
uci:commit("ddns")
|
||||
uci:unload("ddns")
|
||||
|
||||
-- start ddns-updater for section
|
||||
local command = "%s -S %s -- start" %{ luci_helper, UTIL.shellquote(section) }
|
||||
os.execute(command)
|
||||
NX.nanosleep(3) -- 3 seconds "show time"
|
||||
|
||||
-- status changed so return full status
|
||||
data = _get_status()
|
||||
HTTP.prepare_content("application/json")
|
||||
HTTP.write_json(data)
|
||||
end
|
||||
|
||||
-- called by XHR.poll from overview_status.htm
|
||||
function status()
|
||||
local data = _get_status()
|
||||
HTTP.prepare_content("application/json")
|
||||
HTTP.write_json(data)
|
||||
end
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,121 +0,0 @@
|
|||
-- Copyright 2014 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local NX = require "nixio"
|
||||
local NXFS = require "nixio.fs"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local SYS = require "luci.sys"
|
||||
local CTRL = require "luci.controller.ddns" -- this application's controller
|
||||
local DDNS = require "luci.tools.ddns" -- ddns multiused functions
|
||||
|
||||
-- cbi-map definition -- #######################################################
|
||||
local m = Map("ddns")
|
||||
m.title = CTRL.app_title_back()
|
||||
m.description = CTRL.app_description()
|
||||
m.redirect = DISP.build_url("admin", "services", "ddns")
|
||||
|
||||
function m.commit_handler(self)
|
||||
if self.changed then -- changes ?
|
||||
local command = CTRL.luci_helper .. " -- reload"
|
||||
os.execute(command) -- reload configuration
|
||||
end
|
||||
end
|
||||
|
||||
-- cbi-section definition -- ###################################################
|
||||
local ns = m:section( NamedSection, "global", "ddns",
|
||||
translate("Global Settings"),
|
||||
translate("Configure here the details for all Dynamic DNS services including this LuCI application.")
|
||||
.. [[<br /><strong>]]
|
||||
.. translate("It is NOT recommended for casual users to change settings on this page.")
|
||||
.. [[</strong><br />]]
|
||||
.. [[<a href="https://openwrt.org/docs/guide-user/base-system/ddns#section_ddns" target="_blank">]]
|
||||
.. translate("For detailed information about parameter settings look here.")
|
||||
.. [[</a>]]
|
||||
)
|
||||
|
||||
-- section might not exist
|
||||
function ns.cfgvalue(self, section)
|
||||
if not self.map:get(section) then
|
||||
self.map:set(section, nil, self.sectiontype)
|
||||
end
|
||||
return self.map:get(section)
|
||||
end
|
||||
|
||||
-- upd_privateip -- ###########################################################
|
||||
local ali = ns:option(Flag, "upd_privateip")
|
||||
ali.title = translate("Allow non-public IP's")
|
||||
ali.description = translate("Non-public and by default blocked IP's") .. ":"
|
||||
.. [[<br /><strong>IPv4: </strong>]]
|
||||
.. "0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192.168/16"
|
||||
.. [[<br /><strong>IPv6: </strong>]]
|
||||
.. "::/32, f000::/4"
|
||||
ali.default = "0"
|
||||
|
||||
-- ddns_dateformat -- #########################################################
|
||||
local df = ns:option(Value, "ddns_dateformat")
|
||||
df.title = translate("Date format")
|
||||
df.description = [[<a href="http://www.cplusplus.com/reference/ctime/strftime/" target="_blank">]]
|
||||
.. translate("For supported codes look here")
|
||||
.. [[</a>]]
|
||||
df.template = "ddns/global_value"
|
||||
df.default = "%F %R"
|
||||
df.date_string = ""
|
||||
function df.cfgvalue(self, section)
|
||||
local value = AbstractValue.cfgvalue(self, section) or self.default
|
||||
local epoch = os.time()
|
||||
self.date_string = DDNS.epoch2date(epoch, value)
|
||||
return value
|
||||
end
|
||||
function df.parse(self, section, novld)
|
||||
DDNS.value_parse(self, section, novld)
|
||||
end
|
||||
|
||||
-- ddns_rundir -- #############################################################
|
||||
local rd = ns:option(Value, "ddns_rundir")
|
||||
rd.title = translate("Status directory")
|
||||
rd.description = translate("Directory contains PID and other status information for each running section")
|
||||
rd.default = "/var/run/ddns"
|
||||
-- no need to validate. if empty default is used everything else created by dns-scripts
|
||||
function rd.parse(self, section, novld)
|
||||
DDNS.value_parse(self, section, novld)
|
||||
end
|
||||
|
||||
-- ddns_logdir -- #############################################################
|
||||
local ld = ns:option(Value, "ddns_logdir")
|
||||
ld.title = translate("Log directory")
|
||||
ld.description = translate("Directory contains Log files for each running section")
|
||||
ld.default = "/var/log/ddns"
|
||||
-- no need to validate. if empty default is used everything else created by dns-scripts
|
||||
function ld.parse(self, section, novld)
|
||||
DDNS.value_parse(self, section, novld)
|
||||
end
|
||||
|
||||
-- ddns_loglines -- ###########################################################
|
||||
local ll = ns:option(Value, "ddns_loglines")
|
||||
ll.title = translate("Log length")
|
||||
ll.description = translate("Number of last lines stored in log files")
|
||||
ll.default = "250"
|
||||
function ll.validate(self, value)
|
||||
local n = tonumber(value)
|
||||
if not n or math.floor(n) ~= n or n < 1 then
|
||||
return nil, self.title .. ": " .. translate("minimum value '1'")
|
||||
end
|
||||
return value
|
||||
end
|
||||
function ll.parse(self, section, novld)
|
||||
DDNS.value_parse(self, section, novld)
|
||||
end
|
||||
|
||||
-- use_curl -- ################################################################
|
||||
if (SYS.call([[ grep -i "\+ssl" /usr/bin/wget >/dev/null 2>&1 ]]) == 0)
|
||||
and NXFS.access("/usr/bin/curl") then
|
||||
local pc = ns:option(Flag, "use_curl")
|
||||
pc.title = translate("Use cURL")
|
||||
pc.description = translate("If both cURL and GNU Wget are installed, Wget is used by default.")
|
||||
.. [[<br />]]
|
||||
.. translate("To use cURL activate this option.")
|
||||
pc.orientation = "horizontal"
|
||||
pc.default = "0"
|
||||
end
|
||||
|
||||
return m
|
|
@ -1,166 +0,0 @@
|
|||
-- Copyright 2014-2016 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local DISP = require "luci.dispatcher"
|
||||
local SYS = require "luci.sys"
|
||||
local CTRL = require "luci.controller.ddns" -- this application's controller
|
||||
local DDNS = require "luci.tools.ddns" -- ddns multiused functions
|
||||
|
||||
-- html constants
|
||||
font_red = [[<font color="red">]]
|
||||
font_off = [[</font>]]
|
||||
bold_on = [[<strong>]]
|
||||
bold_off = [[</strong>]]
|
||||
|
||||
-- cbi-map definition -- #######################################################
|
||||
m = Map("ddns")
|
||||
m.title = CTRL.app_title_back()
|
||||
m.description = CTRL.app_description()
|
||||
m.redirect = DISP.build_url("admin", "services", "ddns")
|
||||
|
||||
-- SimpleSection definition -- #################################################
|
||||
-- show Hints to optimize installation and script usage
|
||||
s = m:section( SimpleSection,
|
||||
translate("Hints"),
|
||||
translate("Below a list of configuration tips for your system to run Dynamic DNS updates without limitations") )
|
||||
|
||||
-- ddns-scripts needs to be updated for full functionality
|
||||
if not CTRL.service_ok() then
|
||||
local so = s:option(DummyValue, "_update_needed")
|
||||
so.titleref = DISP.build_url("admin", "system", "opkg")
|
||||
so.rawhtml = true
|
||||
so.title = font_red .. bold_on ..
|
||||
translate("Software update required") .. bold_off .. font_off
|
||||
so.value = translate("The currently installed 'ddns-scripts' package did not support all available settings.") ..
|
||||
"<br />" ..
|
||||
translate("Please update to the current version!")
|
||||
end
|
||||
|
||||
-- DDNS Service disabled
|
||||
if not SYS.init.enabled("ddns") then
|
||||
local se = s:option(DummyValue, "_not_enabled")
|
||||
se.titleref = DISP.build_url("admin", "system", "startup")
|
||||
se.rawhtml = true
|
||||
se.title = bold_on ..
|
||||
translate("DDNS Autostart disabled") .. bold_off
|
||||
se.value = translate("Currently DDNS updates are not started at boot or on interface events." .. "<br />" ..
|
||||
"This is the default if you run DDNS scripts by yourself (i.e. via cron with force_interval set to '0')" )
|
||||
end
|
||||
|
||||
-- No IPv6 support
|
||||
if not DDNS.env_info("has_ipv6") then
|
||||
local v6 = s:option(DummyValue, "_no_ipv6")
|
||||
v6.titleref = 'http://www.openwrt.org" target="_blank'
|
||||
v6.rawhtml = true
|
||||
v6.title = bold_on ..
|
||||
translate("IPv6 not supported") .. bold_off
|
||||
v6.value = translate("IPv6 is currently not (fully) supported by this system" .. "<br />" ..
|
||||
"Please follow the instructions on OpenWrt's homepage to enable IPv6 support" .. "<br />" ..
|
||||
"or update your system to the latest OpenWrt Release")
|
||||
end
|
||||
|
||||
-- No HTTPS support
|
||||
if not DDNS.env_info("has_ssl") then
|
||||
local sl = s:option(DummyValue, "_no_https")
|
||||
sl.titleref = DISP.build_url("admin", "system", "opkg")
|
||||
sl.rawhtml = true
|
||||
sl.title = bold_on ..
|
||||
translate("HTTPS not supported") .. bold_off
|
||||
sl.value = translate("Neither GNU Wget with SSL nor cURL installed to support secure updates via HTTPS protocol.") ..
|
||||
"<br />- " ..
|
||||
translate("You should install 'wget' or 'curl' or 'uclient-fetch' with 'libustream-*ssl' package.") ..
|
||||
"<br />- " ..
|
||||
translate("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")
|
||||
end
|
||||
|
||||
-- No bind_network
|
||||
if not DDNS.env_info("has_bindnet") then
|
||||
local bn = s:option(DummyValue, "_no_bind_network")
|
||||
bn.titleref = DISP.build_url("admin", "system", "opkg")
|
||||
bn.rawhtml = true
|
||||
bn.title = bold_on ..
|
||||
translate("Binding to a specific network not supported") .. bold_off
|
||||
bn.value = translate("Neither GNU Wget with SSL nor cURL installed to select a network to use for communication.") ..
|
||||
"<br />- " ..
|
||||
translate("You should install 'wget' or 'curl' package.") ..
|
||||
"<br />- " ..
|
||||
translate("GNU Wget will use the IP of given network, cURL will use the physical interface.") ..
|
||||
"<br />- " ..
|
||||
translate("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")
|
||||
end
|
||||
|
||||
-- currently only cURL possibly without proxy support
|
||||
if not DDNS.env_info("has_proxy") then
|
||||
local px = s:option(DummyValue, "_no_proxy")
|
||||
px.titleref = DISP.build_url("admin", "system", "opkg")
|
||||
px.rawhtml = true
|
||||
px.title = bold_on ..
|
||||
translate("cURL without Proxy Support") .. bold_off
|
||||
px.value = translate("cURL is installed, but libcurl was compiled without proxy support.") ..
|
||||
"<br />- " ..
|
||||
translate("You should install 'wget' or 'uclient-fetch' package or replace libcurl.") ..
|
||||
"<br />- " ..
|
||||
translate("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")
|
||||
end
|
||||
|
||||
-- "Force IP Version not supported"
|
||||
if not DDNS.env_info("has_forceip") then
|
||||
local fi = s:option(DummyValue, "_no_force_ip")
|
||||
fi.titleref = DISP.build_url("admin", "system", "opkg")
|
||||
fi.rawhtml = true
|
||||
fi.title = bold_on ..
|
||||
translate("Force IP Version not supported") .. bold_off
|
||||
local value = translate("BusyBox's nslookup and Wget do not support to specify " ..
|
||||
"the IP version to use for communication with DDNS Provider!")
|
||||
if not (DDNS.env_info("has_wgetssl") or DDNS.env_info("has_curl") or DDNS.env_info("has_fetch")) then
|
||||
value = value .. "<br />- " ..
|
||||
translate("You should install 'wget' or 'curl' or 'uclient-fetch' package.")
|
||||
end
|
||||
if not DDNS.env_info("has_bindhost") then
|
||||
value = value .. "<br />- " ..
|
||||
translate("You should install 'bind-host' or 'knot-host' or 'drill' package for DNS requests.")
|
||||
end
|
||||
fi.value = value
|
||||
end
|
||||
|
||||
-- "DNS requests via TCP not supported"
|
||||
if not DDNS.env_info("has_bindhost") then
|
||||
local dt = s:option(DummyValue, "_no_dnstcp")
|
||||
dt.titleref = DISP.build_url("admin", "system", "opkg")
|
||||
dt.rawhtml = true
|
||||
dt.title = bold_on ..
|
||||
translate("DNS requests via TCP not supported") .. bold_off
|
||||
dt.value = translate("BusyBox's nslookup and hostip do not support to specify to use TCP " ..
|
||||
"instead of default UDP when requesting DNS server!") ..
|
||||
"<br />- " ..
|
||||
translate("You should install 'bind-host' or 'knot-host' or 'drill' package for DNS requests.")
|
||||
end
|
||||
|
||||
-- nslookup compiled with musl produce problems when using
|
||||
if not DDNS.env_info("has_dnsserver") then
|
||||
local ds = s:option(DummyValue, "_no_dnsserver")
|
||||
ds.titleref = DISP.build_url("admin", "system", "opkg")
|
||||
ds.rawhtml = true
|
||||
ds.title = bold_on ..
|
||||
translate("Using specific DNS Server not supported") .. bold_off
|
||||
ds.value = translate("BusyBox's nslookup in the current compiled version " ..
|
||||
"does not handle given DNS Servers correctly!") ..
|
||||
"<br />- " ..
|
||||
translate("You should install 'bind-host' or 'knot-host' or 'drill' or 'hostip' package, " ..
|
||||
"if you need to specify a DNS server to detect your registered IP.")
|
||||
end
|
||||
|
||||
-- certificates installed
|
||||
if DDNS.env_info("has_ssl") and not DDNS.env_info("has_cacerts") then
|
||||
local ca = s:option(DummyValue, "_no_certs")
|
||||
ca.titleref = DISP.build_url("admin", "system", "opkg")
|
||||
ca.rawhtml = true
|
||||
ca.title = bold_on ..
|
||||
translate("No certificates found") .. bold_off
|
||||
ca.value = translate("If using secure communication you should verify server certificates!") ..
|
||||
"<br />- " ..
|
||||
translate("Install 'ca-certificates' package or needed certificates " ..
|
||||
"by hand into /etc/ssl/certs default directory")
|
||||
end
|
||||
|
||||
return m
|
|
@ -1,249 +0,0 @@
|
|||
-- Copyright 2014-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local DISP = require "luci.dispatcher"
|
||||
local HTTP = require "luci.http"
|
||||
local SYS = require "luci.sys"
|
||||
local CTRL = require "luci.controller.ddns" -- this application's controller
|
||||
local DDNS = require "luci.tools.ddns" -- ddns multiused functions
|
||||
|
||||
local show_hints = not (DDNS.env_info("has_ipv6") -- IPv6 support
|
||||
and DDNS.env_info("has_ssl") -- HTTPS support
|
||||
and DDNS.env_info("has_proxy") -- Proxy support
|
||||
and DDNS.env_info("has_bindhost") -- DNS TCP support
|
||||
and DDNS.env_info("has_forceip") -- Force IP version
|
||||
and DDNS.env_info("has_dnsserver") -- DNS server support
|
||||
and DDNS.env_info("has_bindnet") -- Bind to network/interface
|
||||
and DDNS.env_info("has_cacerts") -- certificates installed at /etc/ssl/certs
|
||||
)
|
||||
local not_enabled = not SYS.init.enabled("ddns")
|
||||
local need_update = not CTRL.service_ok()
|
||||
|
||||
-- html constants
|
||||
font_red = [[<font color="red">]]
|
||||
font_off = [[</font>]]
|
||||
bold_on = [[<strong>]]
|
||||
bold_off = [[</strong>]]
|
||||
|
||||
-- cbi-map definition -- #######################################################
|
||||
m = Map("ddns")
|
||||
m.title = CTRL.app_title_main()
|
||||
m.description = CTRL.app_description()
|
||||
|
||||
m.on_after_commit = function(self)
|
||||
if self.changed then -- changes ?
|
||||
local command = CTRL.luci_helper
|
||||
if SYS.init.enabled("ddns") then -- ddns service enabled, restart all
|
||||
command = command .. " -- restart"
|
||||
os.execute(command)
|
||||
else -- ddns service disabled, send SIGHUP to running
|
||||
command = command .. " -- reload"
|
||||
os.execute(command)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- SimpleSection definition -- ##################################################
|
||||
-- with all the JavaScripts we need for "a good Show"
|
||||
a = m:section( SimpleSection )
|
||||
a.template = "ddns/overview_status"
|
||||
|
||||
-- SimpleSection definition -- #################################################
|
||||
-- show Hints to optimize installation and script usage
|
||||
if show_hints or need_update or not_enabled then
|
||||
|
||||
s = m:section( SimpleSection, translate("Hints") )
|
||||
|
||||
-- ddns-scripts needs to be updated for full functionality
|
||||
if need_update then
|
||||
local dv = s:option(DummyValue, "_update_needed")
|
||||
dv.titleref = DISP.build_url("admin", "system", "packages")
|
||||
dv.rawhtml = true
|
||||
dv.title = font_red .. bold_on ..
|
||||
translate("Software update required") .. bold_off .. font_off
|
||||
dv.value = translate("The currently installed 'ddns-scripts' package did not support all available settings.") ..
|
||||
"<br />" ..
|
||||
translate("Please update to the current version!")
|
||||
end
|
||||
|
||||
-- DDNS Service disabled
|
||||
if not_enabled then
|
||||
local dv = s:option(DummyValue, "_not_enabled")
|
||||
dv.titleref = DISP.build_url("admin", "system", "startup")
|
||||
dv.rawhtml = true
|
||||
dv.title = bold_on ..
|
||||
translate("DDNS Autostart disabled") .. bold_off
|
||||
dv.value = translate("Currently DDNS updates are not started at boot or on interface events." .. "<br />" ..
|
||||
"You can start/stop each configuration here. It will run until next reboot.")
|
||||
end
|
||||
|
||||
-- Show more hints on a separate page
|
||||
if show_hints then
|
||||
local dv = s:option(DummyValue, "_separate")
|
||||
dv.titleref = DISP.build_url("admin", "services", "ddns", "hints")
|
||||
dv.rawhtml = true
|
||||
dv.title = bold_on ..
|
||||
translate("Show more") .. bold_off
|
||||
dv.value = translate("Follow this link" .. "<br />" ..
|
||||
"You will find more hints to optimize your system to run DDNS scripts with all options")
|
||||
end
|
||||
end
|
||||
|
||||
-- TableSection definition -- ##################################################
|
||||
ts = m:section( TypedSection, "service",
|
||||
translate("Overview"),
|
||||
translate("Below is a list of configured DDNS configurations and their current state.")
|
||||
.. "<br />"
|
||||
.. translate("If you want to send updates for IPv4 and IPv6 you need to define two separate Configurations "
|
||||
.. "i.e. 'myddns_ipv4' and 'myddns_ipv6'")
|
||||
.. "<br />"
|
||||
.. [[<a href="]] .. DISP.build_url("admin", "services", "ddns", "global") .. [[">]]
|
||||
.. translate("To change global settings click here") .. [[</a>]] )
|
||||
ts.sectionhead = translate("Configuration")
|
||||
ts.template = "cbi/tblsection"
|
||||
ts.addremove = true
|
||||
ts.extedit = DISP.build_url("admin", "services", "ddns", "detail", "%s")
|
||||
function ts.create(self, name)
|
||||
AbstractSection.create(self, name)
|
||||
HTTP.redirect( self.extedit:format(name) )
|
||||
end
|
||||
|
||||
-- Lookup_Host and registered IP -- #################################################
|
||||
dom = ts:option(DummyValue, "_lookupIP",
|
||||
translate("Lookup Hostname") .. "<br />" .. translate("Registered IP") )
|
||||
dom.template = "ddns/overview_doubleline"
|
||||
function dom.set_one(self, section)
|
||||
local lookup = self.map:get(section, "lookup_host") or ""
|
||||
if lookup ~= "" then
|
||||
return lookup
|
||||
else
|
||||
return [[<em>]] .. translate("config error") .. [[</em>]]
|
||||
end
|
||||
end
|
||||
function dom.set_two(self, section)
|
||||
local chk_sec = DDNS.calc_seconds(
|
||||
tonumber(self.map:get(section, "check_interval")) or 10,
|
||||
self.map:get(section, "check_unit") or "minutes" )
|
||||
local ip = DDNS.get_regip(section, chk_sec)
|
||||
if ip == "NOFILE" then
|
||||
local lookup_host = self.map:get(section, "lookup_host") or ""
|
||||
if lookup_host == "" then return "" end
|
||||
local dnsserver = self.map:get(section, "dnsserver") or ""
|
||||
local use_ipv6 = tonumber(self.map:get(section, "use_ipv6") or 0)
|
||||
local force_ipversion = tonumber(self.map:get(section, "force_ipversion") or 0)
|
||||
local force_dnstcp = tonumber(self.map:get(section, "force_dnstcp") or 0)
|
||||
local is_glue = tonumber(self.map:get(section, "is_glue") or 0)
|
||||
local command = CTRL.luci_helper .. [[ -]]
|
||||
if (use_ipv6 == 1) then command = command .. [[6]] end
|
||||
if (force_ipversion == 1) then command = command .. [[f]] end
|
||||
if (force_dnstcp == 1) then command = command .. [[t]] end
|
||||
if (is_glue == 1) then command = command .. [[g]] end
|
||||
command = command .. [[l ]] .. lookup_host
|
||||
command = command .. [[ -S ]] .. section
|
||||
if (#dnsserver > 0) then command = command .. [[ -d ]] .. dnsserver end
|
||||
command = command .. [[ -- get_registered_ip]]
|
||||
ip = SYS.exec(command)
|
||||
end
|
||||
if ip == "" then ip = translate("no data") end
|
||||
return ip
|
||||
end
|
||||
|
||||
-- enabled
|
||||
ena = ts:option( Flag, "enabled",
|
||||
translate("Enabled"))
|
||||
ena.template = "ddns/overview_enabled"
|
||||
ena.rmempty = false
|
||||
|
||||
-- show PID and next update
|
||||
upd = ts:option( DummyValue, "_update",
|
||||
translate("Last Update") .. "<br />" .. translate("Next Update"))
|
||||
upd.template = "ddns/overview_doubleline"
|
||||
function upd.set_one(self, section) -- fill Last Update
|
||||
-- get/validate last update
|
||||
local uptime = SYS.uptime()
|
||||
local lasttime = DDNS.get_lastupd(section)
|
||||
if lasttime > uptime then -- /var might not be linked to /tmp and cleared on reboot
|
||||
lasttime = 0
|
||||
end
|
||||
|
||||
-- no last update happen
|
||||
if lasttime == 0 then
|
||||
return translate("never")
|
||||
|
||||
-- we read last update
|
||||
else
|
||||
-- calc last update
|
||||
-- os.epoch - sys.uptime + lastupdate(uptime)
|
||||
local epoch = os.time() - uptime + lasttime
|
||||
-- use linux date to convert epoch
|
||||
return DDNS.epoch2date(epoch)
|
||||
end
|
||||
end
|
||||
function upd.set_two(self, section) -- fill Next Update
|
||||
-- get enabled state
|
||||
local enabled = tonumber(self.map:get(section, "enabled") or 0)
|
||||
local datenext = translate("unknown error") -- formatted date of next update
|
||||
|
||||
-- get force seconds
|
||||
local force_interval = tonumber(self.map:get(section, "force_interval") or 72)
|
||||
local force_unit = self.map:get(section, "force_unit") or "hours"
|
||||
local force_seconds = DDNS.calc_seconds(force_interval, force_unit)
|
||||
|
||||
-- get last update and get/validate PID
|
||||
local uptime = SYS.uptime()
|
||||
local lasttime = DDNS.get_lastupd(section)
|
||||
if lasttime > uptime then -- /var might not be linked to /tmp and cleared on reboot
|
||||
lasttime = 0
|
||||
end
|
||||
local pid = DDNS.get_pid(section)
|
||||
|
||||
-- calc next update
|
||||
if lasttime > 0 then
|
||||
local epoch = os.time() - uptime + lasttime + force_seconds
|
||||
-- use linux date to convert epoch
|
||||
datelast = DDNS.epoch2date(epoch)
|
||||
end
|
||||
|
||||
-- process running but update needs to happen
|
||||
if pid > 0 and ( lasttime + force_seconds - uptime ) < 0 then
|
||||
datenext = translate("Verify")
|
||||
|
||||
-- run once
|
||||
elseif force_seconds == 0 then
|
||||
datenext = translate("Run once")
|
||||
|
||||
-- no process running and NOT enabled
|
||||
elseif pid == 0 and enabled == 0 then
|
||||
datenext = translate("Disabled")
|
||||
|
||||
-- no process running and NOT
|
||||
elseif pid == 0 and enabled ~= 0 then
|
||||
datenext = translate("Stopped")
|
||||
end
|
||||
|
||||
return datenext
|
||||
end
|
||||
|
||||
-- start/stop button
|
||||
btn = ts:option( Button, "_startstop",
|
||||
translate("Process ID") .. "<br />" .. translate("Start / Stop") )
|
||||
btn.template = "ddns/overview_startstop"
|
||||
function btn.cfgvalue(self, section)
|
||||
local pid = DDNS.get_pid(section)
|
||||
if pid > 0 then
|
||||
btn.inputtitle = "PID: " .. pid
|
||||
btn.inputstyle = "reset"
|
||||
btn.disabled = false
|
||||
elseif (self.map:get(section, "enabled") or "0") ~= "0" then
|
||||
btn.inputtitle = translate("Start")
|
||||
btn.inputstyle = "apply"
|
||||
btn.disabled = false
|
||||
else
|
||||
btn.inputtitle = "----------"
|
||||
btn.inputstyle = "button"
|
||||
btn.disabled = true
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return m
|
|
@ -1,429 +0,0 @@
|
|||
-- Copyright 2014-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
module("luci.tools.ddns", package.seeall)
|
||||
|
||||
local NX = require "nixio"
|
||||
local NXFS = require "nixio.fs"
|
||||
local UCI = require "luci.model.uci"
|
||||
local SYS = require "luci.sys"
|
||||
|
||||
function env_info(type)
|
||||
|
||||
if ( type == "has_ssl" ) or ( type == "has_proxy" ) or ( type == "has_forceip" )
|
||||
or ( type == "has_bindnet" ) or ( type == "has_fetch" )
|
||||
or ( type == "has_wgetssl" ) or ( type == "has_curl" )
|
||||
or ( type == "has_curlssl" ) or ( type == "has_curlpxy" )
|
||||
or ( type == "has_fetchssl" ) or ( type == "has_bbwget" ) then
|
||||
|
||||
local function has_wgetssl()
|
||||
return (SYS.call( [[which wget-ssl >/dev/null 2>&1]] ) == 0) -- and true or nil
|
||||
end
|
||||
|
||||
local function has_curlssl()
|
||||
return (SYS.call( [[$(which curl) -V 2>&1 | grep -qF "https"]] ) == 0)
|
||||
end
|
||||
|
||||
local function has_fetch()
|
||||
return (SYS.call( [[which uclient-fetch >/dev/null 2>&1]] ) == 0)
|
||||
end
|
||||
|
||||
local function has_fetchssl()
|
||||
return NXFS.access("/lib/libustream-ssl.so")
|
||||
end
|
||||
|
||||
local function has_curl()
|
||||
return (SYS.call( [[which curl >/dev/null 2>&1]] ) == 0)
|
||||
end
|
||||
|
||||
local function has_curlpxy()
|
||||
return (SYS.call( [[grep -i "all_proxy" /usr/lib/libcurl.so* >/dev/null 2>&1]] ) == 0)
|
||||
end
|
||||
|
||||
local function has_bbwget()
|
||||
return (SYS.call( [[$(which wget) -V 2>&1 | grep -iqF "busybox"]] ) == 0)
|
||||
end
|
||||
|
||||
if type == "has_wgetssl" then
|
||||
return has_wgetssl()
|
||||
|
||||
elseif type == "has_curl" then
|
||||
return has_curl()
|
||||
|
||||
elseif type == "has_curlssl" then
|
||||
return has_curlssl()
|
||||
|
||||
elseif type == "has_curlpxy" then
|
||||
return has_curlpxy()
|
||||
|
||||
elseif type == "has_fetch" then
|
||||
return has_fetch()
|
||||
|
||||
elseif type == "has_fetchssl" then
|
||||
return has_fetchssl()
|
||||
|
||||
elseif type == "has_bbwget" then
|
||||
return has_bbwget()
|
||||
|
||||
elseif type == "has_ssl" then
|
||||
if has_wgetssl() then return true end
|
||||
if has_curlssl() then return true end
|
||||
if (has_fetch() and has_fetchssl()) then return true end
|
||||
return false
|
||||
|
||||
elseif type == "has_proxy" then
|
||||
if has_wgetssl() then return true end
|
||||
if has_curlpxy() then return true end
|
||||
if has_fetch() then return true end
|
||||
if has_bbwget() then return true end
|
||||
return false
|
||||
|
||||
elseif type == "has_forceip" then
|
||||
if has_wgetssl() then return true end
|
||||
if has_curl() then return true end
|
||||
if has_fetch() then return true end -- only really needed for transfer
|
||||
return false
|
||||
|
||||
elseif type == "has_bindnet" then
|
||||
if has_curl() then return true end
|
||||
if has_wgetssl() then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
elseif ( type == "has_dnsserver" ) or ( type == "has_bindhost" ) or ( type == "has_hostip" ) or ( type == "has_nslookup" ) then
|
||||
local function has_bindhost()
|
||||
if (SYS.call( [[which host >/dev/null 2>&1]] ) == 0) then return true end
|
||||
if (SYS.call( [[which khost >/dev/null 2>&1]] ) == 0) then return true end
|
||||
if (SYS.call( [[which drill >/dev/null 2>&1]] ) == 0) then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
local function has_hostip()
|
||||
return (SYS.call( [[which hostip >/dev/null 2>&1]] ) == 0)
|
||||
end
|
||||
|
||||
local function has_nslookup()
|
||||
return (SYS.call( [[which nslookup >/dev/null 2>&1]] ) == 0)
|
||||
end
|
||||
|
||||
if type == "has_bindhost" then
|
||||
return has_bindhost()
|
||||
elseif type == "has_hostip" then
|
||||
return has_hostip()
|
||||
elseif type == "has_nslookup" then
|
||||
return has_nslookup()
|
||||
elseif type == "has_dnsserver" then
|
||||
if has_bindhost() then return true end
|
||||
if has_hostip() then return true end
|
||||
if has_nslookup() then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
elseif type == "has_ipv6" then
|
||||
return (NXFS.access("/proc/net/ipv6_route") and NXFS.access("/usr/sbin/ip6tables"))
|
||||
|
||||
elseif type == "has_cacerts" then
|
||||
--old _check_certs() local function
|
||||
local _, v = NXFS.glob("/etc/ssl/certs/*.crt")
|
||||
if ( v == 0 ) then _, v = NXFS.glob("/etc/ssl/certs/*.pem") end
|
||||
return (v > 0)
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- function to calculate seconds from given interval and unit
|
||||
function calc_seconds(interval, unit)
|
||||
if not tonumber(interval) then
|
||||
return nil
|
||||
elseif unit == "days" then
|
||||
return (tonumber(interval) * 86400) -- 60 sec * 60 min * 24 h
|
||||
elseif unit == "hours" then
|
||||
return (tonumber(interval) * 3600) -- 60 sec * 60 min
|
||||
elseif unit == "minutes" then
|
||||
return (tonumber(interval) * 60) -- 60 sec
|
||||
elseif unit == "seconds" then
|
||||
return tonumber(interval)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- convert epoch date to given format
|
||||
function epoch2date(epoch, format)
|
||||
if not format or #format < 2 then
|
||||
local uci = UCI.cursor()
|
||||
format = uci:get("ddns", "global", "ddns_dateformat") or "%F %R"
|
||||
uci:unload("ddns")
|
||||
end
|
||||
format = format:gsub("%%n", "<br />") -- replace newline
|
||||
format = format:gsub("%%t", " ") -- replace tab
|
||||
return os.date(format, epoch)
|
||||
end
|
||||
|
||||
-- read lastupdate from [section].update file
|
||||
function get_lastupd(section)
|
||||
local uci = UCI.cursor()
|
||||
local rdir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
|
||||
local etime = tonumber(NXFS.readfile("%s/%s.update" % { rdir, section } ) or 0 )
|
||||
uci:unload("ddns")
|
||||
return etime
|
||||
end
|
||||
|
||||
-- read registered IP from [section].ip file
|
||||
function get_regip(section, chk_sec)
|
||||
local uci = UCI.cursor()
|
||||
local rdir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
|
||||
local ip = "NOFILE"
|
||||
if NXFS.access("%s/%s.ip" % { rdir, section }) then
|
||||
local ftime = NXFS.stat("%s/%s.ip" % { rdir, section }, "ctime") or 0
|
||||
local otime = os.time()
|
||||
-- give ddns-scripts time (9 sec) to update file
|
||||
if otime < (ftime + chk_sec + 9) then
|
||||
ip = NXFS.readfile("%s/%s.ip" % { rdir, section })
|
||||
end
|
||||
end
|
||||
uci:unload("ddns")
|
||||
return ip
|
||||
end
|
||||
|
||||
-- read PID from run file and verify if still running
|
||||
function get_pid(section)
|
||||
local uci = UCI.cursor()
|
||||
local rdir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
|
||||
local pid = tonumber(NXFS.readfile("%s/%s.pid" % { rdir, section } ) or 0 )
|
||||
if pid > 0 and not NX.kill(pid, 0) then
|
||||
pid = 0
|
||||
end
|
||||
uci:unload("ddns")
|
||||
return pid
|
||||
end
|
||||
|
||||
-- replacement of build-in read of UCI option
|
||||
-- modified AbstractValue.cfgvalue(self, section) from cbi.lua
|
||||
-- needed to read from other option then current value definition
|
||||
function read_value(self, section, option)
|
||||
local value
|
||||
if self.tag_error[section] then
|
||||
value = self:formvalue(section)
|
||||
else
|
||||
value = self.map:get(section, option)
|
||||
end
|
||||
|
||||
if not value then
|
||||
return nil
|
||||
elseif not self.cast or self.cast == type(value) then
|
||||
return value
|
||||
elseif self.cast == "string" then
|
||||
if type(value) == "table" then
|
||||
return value[1]
|
||||
end
|
||||
elseif self.cast == "table" then
|
||||
return { value }
|
||||
end
|
||||
end
|
||||
|
||||
-- replacement of build-in parse of "Value"
|
||||
-- modified AbstractValue.parse(self, section, novld) from cbi.lua
|
||||
-- validate is called if rmempty/optional true or false
|
||||
-- before write check if forcewrite, value eq default, and more
|
||||
function value_parse(self, section, novld)
|
||||
local fvalue = self:formvalue(section)
|
||||
local fexist = ( fvalue and (#fvalue > 0) ) -- not "nil" and "not empty"
|
||||
local cvalue = self:cfgvalue(section)
|
||||
local rm_opt = ( self.rmempty or self.optional )
|
||||
local eq_cfg -- flag: equal cfgvalue
|
||||
|
||||
-- If favlue and cvalue are both tables and have the same content
|
||||
-- make them identical
|
||||
if type(fvalue) == "table" and type(cvalue) == "table" then
|
||||
eq_cfg = (#fvalue == #cvalue)
|
||||
if eq_cfg then
|
||||
for i=1, #fvalue do
|
||||
if cvalue[i] ~= fvalue[i] then
|
||||
eq_cfg = false
|
||||
end
|
||||
end
|
||||
end
|
||||
if eq_cfg then
|
||||
fvalue = cvalue
|
||||
end
|
||||
end
|
||||
|
||||
-- removed parameter "section" from function call because used/accepted nowhere
|
||||
-- also removed call to function "transfer"
|
||||
local vvalue, errtxt = self:validate(fvalue)
|
||||
|
||||
-- error handling; validate return "nil"
|
||||
if not vvalue then
|
||||
if novld then -- and "novld" set
|
||||
return -- then exit without raising an error
|
||||
end
|
||||
|
||||
if fexist then -- and there is a formvalue
|
||||
self:add_error(section, "invalid", errtxt or self.title .. ": invalid")
|
||||
return -- so data are invalid
|
||||
elseif not rm_opt then -- and empty formvalue but NOT (rmempty or optional) set
|
||||
self:add_error(section, "missing", errtxt or self.title .. ": missing")
|
||||
return -- so data is missing
|
||||
elseif errtxt then
|
||||
self:add_error(section, "invalid", errtxt)
|
||||
return
|
||||
end
|
||||
-- error ("\n option: " .. self.option ..
|
||||
-- "\n fvalue: " .. tostring(fvalue) ..
|
||||
-- "\n fexist: " .. tostring(fexist) ..
|
||||
-- "\n cvalue: " .. tostring(cvalue) ..
|
||||
-- "\n vvalue: " .. tostring(vvalue) ..
|
||||
-- "\n vexist: " .. tostring(vexist) ..
|
||||
-- "\n rm_opt: " .. tostring(rm_opt) ..
|
||||
-- "\n eq_cfg: " .. tostring(eq_cfg) ..
|
||||
-- "\n eq_def: " .. tostring(eq_def) ..
|
||||
-- "\n novld : " .. tostring(novld) ..
|
||||
-- "\n errtxt: " .. tostring(errtxt) )
|
||||
end
|
||||
|
||||
-- lets continue with value returned from validate
|
||||
eq_cfg = ( vvalue == cvalue ) -- update equal_config flag
|
||||
local vexist = ( vvalue and (#vvalue > 0) ) and true or false -- not "nil" and "not empty"
|
||||
local eq_def = ( vvalue == self.default ) -- equal_default flag
|
||||
|
||||
-- (rmempty or optional) and (no data or equal_default)
|
||||
if rm_opt and (not vexist or eq_def) then
|
||||
if self:remove(section) then -- remove data from UCI
|
||||
self.section.changed = true -- and push events
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- not forcewrite and no changes, so nothing to write
|
||||
if not self.forcewrite and eq_cfg then
|
||||
return
|
||||
end
|
||||
|
||||
-- we should have a valid value here
|
||||
assert (vvalue, "\n option: " .. self.option ..
|
||||
"\n fvalue: " .. tostring(fvalue) ..
|
||||
"\n fexist: " .. tostring(fexist) ..
|
||||
"\n cvalue: " .. tostring(cvalue) ..
|
||||
"\n vvalue: " .. tostring(vvalue) ..
|
||||
"\n vexist: " .. tostring(vexist) ..
|
||||
"\n rm_opt: " .. tostring(rm_opt) ..
|
||||
"\n eq_cfg: " .. tostring(eq_cfg) ..
|
||||
"\n eq_def: " .. tostring(eq_def) ..
|
||||
"\n errtxt: " .. tostring(errtxt) )
|
||||
|
||||
-- write data to UCI; raise event only on changes
|
||||
if self:write(section, vvalue) and not eq_cfg then
|
||||
self.section.changed = true
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- copied from https://svn.nmap.org/nmap/nselib/url.lua
|
||||
-- @author Diego Nehab
|
||||
-- @author Eddie Bell <ejlbell@gmail.com>
|
||||
--[[
|
||||
URI parsing, composition and relative URL resolution
|
||||
LuaSocket toolkit.
|
||||
Author: Diego Nehab
|
||||
RCS ID: $Id: url.lua,v 1.37 2005/11/22 08:33:29 diego Exp $
|
||||
parse_query and build_query added For nmap (Eddie Bell <ejlbell@gmail.com>)
|
||||
]]--
|
||||
---
|
||||
-- Parses a URL and returns a table with all its parts according to RFC 2396.
|
||||
--
|
||||
-- The following grammar describes the names given to the URL parts.
|
||||
-- <code>
|
||||
-- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
|
||||
-- <authority> ::= <userinfo>@<host>:<port>
|
||||
-- <userinfo> ::= <user>[:<password>]
|
||||
-- <path> :: = {<segment>/}<segment>
|
||||
-- </code>
|
||||
--
|
||||
-- The leading <code>/</code> in <code>/<path></code> is considered part of
|
||||
-- <code><path></code>.
|
||||
-- @param url URL of request.
|
||||
-- @param default Table with default values for each field.
|
||||
-- @return A table with the following fields, where RFC naming conventions have
|
||||
-- been preserved:
|
||||
-- <code>scheme</code>, <code>authority</code>, <code>userinfo</code>,
|
||||
-- <code>user</code>, <code>password</code>, <code>host</code>,
|
||||
-- <code>port</code>, <code>path</code>, <code>params</code>,
|
||||
-- <code>query</code>, and <code>fragment</code>.
|
||||
-----------------------------------------------------------------------------
|
||||
function parse_url(url) --, default)
|
||||
-- initialize default parameters
|
||||
local parsed = {}
|
||||
-- for i,v in base.pairs(default or parsed) do
|
||||
-- parsed[i] = v
|
||||
-- end
|
||||
|
||||
-- remove whitespace
|
||||
-- url = string.gsub(url, "%s", "")
|
||||
-- get fragment
|
||||
url = string.gsub(url, "#(.*)$",
|
||||
function(f)
|
||||
parsed.fragment = f
|
||||
return ""
|
||||
end)
|
||||
-- get scheme. Lower-case according to RFC 3986 section 3.1.
|
||||
url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
|
||||
function(s)
|
||||
parsed.scheme = string.lower(s);
|
||||
return ""
|
||||
end)
|
||||
-- get authority
|
||||
url = string.gsub(url, "^//([^/]*)",
|
||||
function(n)
|
||||
parsed.authority = n
|
||||
return ""
|
||||
end)
|
||||
-- get query stringing
|
||||
url = string.gsub(url, "%?(.*)",
|
||||
function(q)
|
||||
parsed.query = q
|
||||
return ""
|
||||
end)
|
||||
-- get params
|
||||
url = string.gsub(url, "%;(.*)",
|
||||
function(p)
|
||||
parsed.params = p
|
||||
return ""
|
||||
end)
|
||||
-- path is whatever was left
|
||||
parsed.path = url
|
||||
|
||||
local authority = parsed.authority
|
||||
if not authority then
|
||||
return parsed
|
||||
end
|
||||
authority = string.gsub(authority,"^([^@]*)@",
|
||||
function(u)
|
||||
parsed.userinfo = u;
|
||||
return ""
|
||||
end)
|
||||
authority = string.gsub(authority, ":([0-9]*)$",
|
||||
function(p)
|
||||
if p ~= "" then
|
||||
parsed.port = p
|
||||
end;
|
||||
return ""
|
||||
end)
|
||||
if authority ~= "" then
|
||||
parsed.host = authority
|
||||
end
|
||||
|
||||
local userinfo = parsed.userinfo
|
||||
if not userinfo then
|
||||
return parsed
|
||||
end
|
||||
userinfo = string.gsub(userinfo, ":([^:]*)$",
|
||||
function(p)
|
||||
parsed.password = p;
|
||||
return ""
|
||||
end)
|
||||
parsed.user = userinfo
|
||||
return parsed
|
||||
end
|
|
@ -1 +0,0 @@
|
|||
<%+ddns/system_status%>
|
|
@ -1,56 +0,0 @@
|
|||
|
||||
<!-- ++ BEGIN ++ Dynamic DNS ++ detail_logview.htm ++ -->
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function onclick_logview(section, bottom) {
|
||||
// get elements
|
||||
var txt = document.getElementById("cbid.ddns." + section + "._logview.txt"); // TextArea
|
||||
if ( !txt ) { return; } // security check
|
||||
|
||||
XHR.get('<%=url([[admin]], [[services]], [[ddns]], [[logview]])%>/' + section, null,
|
||||
function(x) {
|
||||
if (x.responseText == "_nodata_")
|
||||
txt.value = "<%:File not found or empty%>";
|
||||
else
|
||||
txt.value = x.responseText;
|
||||
if (bottom)
|
||||
txt.scrollTop = txt.scrollHeight;
|
||||
else
|
||||
txt.scrollTop = 0; }
|
||||
);
|
||||
}
|
||||
//]]></script>
|
||||
|
||||
<%+cbi/valueheader%>
|
||||
|
||||
<br />
|
||||
|
||||
<%
|
||||
-- one button on top, one at the buttom
|
||||
%>
|
||||
<input class="cbi-button cbi-input-button" style="align: center; width: 100%" type="button" onclick="onclick_logview(this.name, false)"
|
||||
<%=
|
||||
attr("name", section) .. attr("id", cbid .. ".btn1") .. attr("value", self.inputtitle)
|
||||
%> />
|
||||
|
||||
<br /><br />
|
||||
|
||||
<%
|
||||
-- set a readable style taken from openwrt theme for textarea#syslog
|
||||
-- in openwrt theme there are problems with a width of 100 so we check for theme and set to lower value
|
||||
%>
|
||||
<textarea style="width: <%if media == "/luci-static/openwrt.org" then%>98.7%<%else%>100%<%end%> ; min-height: 500px; border: 3px solid #cccccc; padding: 5px; font-family: monospace; resize: none;" wrap="off" readonly="readonly"
|
||||
<%=
|
||||
attr("name", cbid .. ".txt") .. attr("id", cbid .. ".txt") .. ifattr(self.rows, "rows")
|
||||
%> >
|
||||
<%-=pcdata(self:cfgvalue(section))-%>
|
||||
</textarea>
|
||||
<br /><br />
|
||||
|
||||
<%
|
||||
-- one button on top, one at the buttom
|
||||
%>
|
||||
<input class="cbi-button cbi-input-button" style="align: center; width: 100%" type="button" onclick="onclick_logview(this.name, true)"
|
||||
<%= attr("name", section) .. attr("id", cbid .. ".btn2") .. attr("value", self.inputtitle) %> />
|
||||
|
||||
<%+cbi/valuefooter%>
|
||||
<!-- ++ END ++ Dynamic DNS ++ detail_logview.htm ++ -->
|
|
@ -1,23 +0,0 @@
|
|||
|
||||
<!-- ++ BEGIN ++ Dynamic DNS ++ detail_lvalue.htm ++ -->
|
||||
<!-- no value header to suppress next line -->
|
||||
 
|
||||
<% if self.widget == "select" then %>
|
||||
<select class="cbi-input-select" onchange="cbi_d_update(this.id)"<%= attr("id", cbid) .. attr("name", cbid) .. ifattr(self.size, "size") %>>
|
||||
<% for i, key in pairs(self.keylist) do -%>
|
||||
<option id="cbi-<%=self.config.."-"..section.."-"..self.option.."-"..key%>"<%= attr("value", key) .. ifattr(tostring(self:cfgvalue(section) or self.default) == key, "selected", "selected") %>><%=striptags(self.vallist[i])%></option>
|
||||
<%- end %>
|
||||
</select>
|
||||
<% elseif self.widget == "radio" then
|
||||
local c = 0
|
||||
for i, key in pairs(self.keylist) do
|
||||
c = c + 1
|
||||
%>
|
||||
<input class="cbi-input-radio" onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)" type="radio"<%= attr("id", cbid..c) .. attr("name", cbid) .. attr("value", key) .. ifattr((self:cfgvalue(section) or self.default) == key, "checked", "checked") %> />
|
||||
<label<%= attr("for", cbid..c) %>></label>
|
||||
<label<%= attr("for", cbid..c) %>><%=self.vallist[i]%></label>
|
||||
<% if c == self.size then c = 0 %><% if self.orientation == "horizontal" then %> <% else %><br /><% end %>
|
||||
<% end end %>
|
||||
<% end %>
|
||||
<%+cbi/valuefooter%>
|
||||
<!-- ++ END ++ Dynamic DNS ++ detail_lvalue.htm ++ -->
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
<!-- ++ BEGIN ++ Dynamic DNS ++ detail_value.htm ++ -->
|
||||
<%+cbi/valueheader%>
|
||||
<input type="text" class="cbi-input-text" style="width: 10em;" onchange="cbi_d_update(this.id)"<%=
|
||||
attr("name", cbid) .. attr("id", cbid) .. attr("value", self:cfgvalue(section) or self.default) ..
|
||||
ifattr(self.size, "size") .. ifattr(self.placeholder, "placeholder")
|
||||
%> />
|
||||
<!-- no value footer to suppress next line -->
|
||||
<!-- ++ END ++ Dynamic DNS ++ detail_value.htm ++ -->
|
|
@ -1,33 +0,0 @@
|
|||
|
||||
<!-- ++ BEGIN ++ Dynamic DNS ++ global_value.htm ++ -->
|
||||
<%+cbi/valueheader%>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
// event handler on changed date
|
||||
function onkeyup_date(value) {
|
||||
var obj = document.getElementById("cbid.ddns.global.ddns_dateformat.help");
|
||||
if ( !obj ) { return; } // security check
|
||||
|
||||
if ( value == "" || value.length == 0 ) { value = "%F %R"; }
|
||||
var now = new Date();
|
||||
var txt = now.toLocaleFormat(value);
|
||||
// handle newline(%n) and tab(%t) needs to be converted to HTML
|
||||
txt = txt.replace(new RegExp('\r?\n','g'), '<br />');
|
||||
txt = txt.replace(new RegExp('\t','g'), ' ');
|
||||
obj.innerHTML = "<%:Current setting%>: <strong>" + txt + "<\/strong>";
|
||||
}
|
||||
//]]></script>
|
||||
|
||||
<input type="text" class="cbi-input-text" onchange="cbi_d_update(this.id)" onkeyup="onkeyup_date(this.value)"
|
||||
<%=
|
||||
attr("name", cbid) .. attr("id", cbid) .. attr("value", self:cfgvalue(section) or self.default) ..
|
||||
ifattr(self.size, "size") .. ifattr(self.placeholder, "placeholder")
|
||||
%>
|
||||
/>
|
||||
<br />
|
||||
<div class="cbi-value-description">
|
||||
<span class="cbi-value-helpicon"><img src="<%=resource%>/cbi/help.gif" alt="<%:help%>" /></span><%=self.description%>
|
||||
<br /><%:Current setting%>: <strong><%=self.date_string%></strong>
|
||||
</div> <!-- div class="cbi-value-description" -->
|
||||
</div> <!-- div class="cbi-value-field" -->
|
||||
</div> <!-- div class="cbi-value cbi-value-last" -->
|
||||
<!-- ++ END ++ Dynamic DNS ++ global_value.htm ++ -->
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
<!-- ++ BEGIN ++ Dynamic DNS ++ overview_doubleline.htm ++ -->
|
||||
<%+cbi/valueheader%>
|
||||
|
||||
<span id="<%=cbid%>.one"><%=self:set_one(section)%></span>
|
||||
<br />
|
||||
<span id="<%=cbid%>.two"><%=self:set_two(section)%></span>
|
||||
|
||||
<%+cbi/valuefooter%>
|
||||
<!-- ++ END ++ Dynamic DNS ++ overview_doubleline.htm ++ -->
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
<!-- ++ BEGIN ++ Dynamic DNS ++ overview_enabled.htm ++ -->
|
||||
<%+cbi/valueheader%>
|
||||
|
||||
<input type="hidden" value="1"<%=
|
||||
attr("name", "cbi.cbe." .. self.config .. "." .. section .. "." .. self.option)
|
||||
%> />
|
||||
<!-- modified to call own function -->
|
||||
<input class="cbi-input-checkbox" onclick="cbi_d_update(this.id)" onchange="onchange_enabled(this.id)" type="checkbox"<%=
|
||||
attr("id", cbid) .. attr("name", cbid) .. attr("value", self.enabled or 1) ..
|
||||
ifattr((self:cfgvalue(section) or self.default) == self.enabled, "checked", "checked")
|
||||
%> />
|
||||
<label<%= attr("for", cbid)%>></label>
|
||||
|
||||
<%+cbi/valuefooter%>
|
||||
<!-- ++ END ++ Dynamic DNS ++ overview_enabled.htm ++ -->
|
|
@ -1,17 +0,0 @@
|
|||
|
||||
<!-- ++ BEGIN ++ Dynamic DNS ++ overview_startstop.htm ++ -->
|
||||
<%+cbi/valueheader%>
|
||||
|
||||
<% if self:cfgvalue(section) ~= false then
|
||||
-- We need to garantie that function cfgvalue run first to set missing parameters
|
||||
%>
|
||||
<!-- style="font-size: 100%;" needed for openwrt theme to fix font size -->
|
||||
<!-- type="button" onclick="..." enable standard onclick functionality -->
|
||||
<input class="cbi-button cbi-input-<%=self.inputstyle or "button" %>" style="font-size: 100%;" type="button" onclick="onclick_startstop(this.id)"
|
||||
<%=
|
||||
attr("name", section) .. attr("id", cbid) .. attr("value", self.inputtitle) .. ifattr(self.disabled, "disabled")
|
||||
%> />
|
||||
<% end %>
|
||||
|
||||
<%+cbi/valuefooter%>
|
||||
<!-- ++ END ++ Dynamic DNS ++ overview_startstop.htm ++ -->
|
|
@ -1,180 +0,0 @@
|
|||
|
||||
<!-- ++ BEGIN ++ Dynamic DNS ++ overview_status.htm ++ -->
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
|
||||
// helper to extract section from objects id
|
||||
// cbi.ddns.SECTION._xyz
|
||||
function _id2section(id) {
|
||||
var x = id.split(".");
|
||||
return x[2];
|
||||
}
|
||||
|
||||
// helper to move status data to the relevant
|
||||
// screen objects
|
||||
// called by XHR.poll and onclick_startstop
|
||||
function _data2elements(data) {
|
||||
// Service sections
|
||||
for( var i = 1; i < data.length; i++ )
|
||||
{
|
||||
var section = data[i].section // Section to handle
|
||||
var cbx = document.getElementById("cbid.ddns." + section + ".enabled"); // Enabled
|
||||
var btn = document.getElementById("cbid.ddns." + section + "._startstop"); // Start/Stop button
|
||||
var rip = document.getElementById("cbid.ddns." + section + "._lookupIP.two"); // Registered IP
|
||||
var lup = document.getElementById("cbid.ddns." + section + "._update.one"); // Last Update
|
||||
var nup = document.getElementById("cbid.ddns." + section + "._update.two"); // Next Update
|
||||
if ( !(cbx && btn && rip && lup && nup) ) { return; } // security check
|
||||
|
||||
// process id
|
||||
if (data[i].pid > 0) {
|
||||
// stop always possible if process running
|
||||
btn.value = "PID: " + data[i].pid;
|
||||
btn.className = "cbi-button cbi-input-reset";
|
||||
} else {
|
||||
// default Start / enabled
|
||||
btn.value = "<%:Start%>";
|
||||
btn.className = "cbi-button cbi-input-apply";
|
||||
}
|
||||
btn.disabled = false; // button enabled
|
||||
|
||||
// last update
|
||||
switch (data[i].datelast) {
|
||||
case "_empty_":
|
||||
lup.innerHTML = '<em><%:Unknown error%></em>' ;
|
||||
break;
|
||||
case "_never_":
|
||||
lup.innerHTML = '<em><%:Never%></em>' ;
|
||||
break;
|
||||
default:
|
||||
lup.innerHTML = data[i].datelast;
|
||||
break;
|
||||
}
|
||||
|
||||
// next update
|
||||
switch (data[i].datenext) {
|
||||
case "_empty_":
|
||||
nup.innerHTML = '<em><%:Unknown error%></em>' ;
|
||||
break;
|
||||
case "_verify_":
|
||||
nup.innerHTML = '<em><%:Verify%></em>';
|
||||
break;
|
||||
case "_runonce_":
|
||||
case "_stopped_":
|
||||
case "_disabled_":
|
||||
if (cbx.checked && data[i].datenext == "_runonce_") {
|
||||
nup.innerHTML = '<em><%:Run once%></em>';
|
||||
} else if (cbx.checked) {
|
||||
nup.innerHTML = '<em><%:Stopped%></em>';
|
||||
} else {
|
||||
nup.innerHTML = '<em><%:Disabled%></em>';
|
||||
btn.value = '----------';
|
||||
btn.className = "cbi-button cbi-input-button"; // no image
|
||||
btn.disabled = true; // disabled
|
||||
}
|
||||
break;
|
||||
default:
|
||||
nup.innerHTML = data[i].datenext;
|
||||
break;
|
||||
}
|
||||
|
||||
// lookup
|
||||
// (data[i].lookup ignored here
|
||||
|
||||
// registered IP
|
||||
// rip.innerHTML = "Registered IP";
|
||||
if (data[i].lookup == "_nolookup_")
|
||||
rip.innerHTML = '';
|
||||
else if (data[i].reg_ip == "_nodata_")
|
||||
rip.innerHTML = '<em><%:No data%></em>';
|
||||
else
|
||||
rip.innerHTML = data[i].reg_ip;
|
||||
|
||||
// monitored interfacce
|
||||
// data[i].iface ignored here
|
||||
}
|
||||
}
|
||||
|
||||
// event handler for enabled checkbox
|
||||
function onchange_enabled(id) {
|
||||
// run original function in cbi.js
|
||||
// whatever is done there
|
||||
cbi_d_update(id);
|
||||
|
||||
var section = _id2section(id);
|
||||
var cbx = document.getElementById("cbid.ddns." + section + ".enabled");
|
||||
var btn = document.getElementById("cbid.ddns." + section + "._startstop");
|
||||
if ( !(cbx && btn) ) { return; } // security check
|
||||
|
||||
var pid_txt = btn.value;
|
||||
var pid_found = ( pid_txt.search("PID") >= 0 ) ? true : false;
|
||||
|
||||
if (pid_found) {
|
||||
// btn.value = "PID: 0000";
|
||||
btn.className = "cbi-button cbi-button-reset";
|
||||
btn.disabled = false;
|
||||
} else if (cbx.checked) {
|
||||
btn.value = "<%:Start%>";
|
||||
btn.className = "cbi-button cbi-button-apply";
|
||||
btn.disabled = false;
|
||||
} else {
|
||||
btn.value = '----------';
|
||||
btn.className = "cbi-button cbi-input-button"; // no image
|
||||
btn.disabled = true; // disabled
|
||||
}
|
||||
}
|
||||
|
||||
// event handler for start/stop button
|
||||
function onclick_startstop(id) {
|
||||
// extract section
|
||||
var section = _id2section(id);
|
||||
// get elements
|
||||
var cbx = document.getElementById("cbid.ddns." + section + ".enabled"); // Enabled
|
||||
var obj = document.getElementById("cbi-ddns-overview-status-legend"); // object defined below to make in-/visible
|
||||
if ( !(obj && cbx) ) { return; } // security check
|
||||
|
||||
// make me visible
|
||||
obj.parentNode.style.display = "block";
|
||||
|
||||
// do start/stop
|
||||
var btnXHR = new XHR();
|
||||
btnXHR.post('<%=url([[admin]], [[services]], [[ddns]], [[startstop]])%>/' + section + '/' + cbx.checked, { token: '<%=token%>' },
|
||||
function(x, data) {
|
||||
if (x.responseText == "_uncommitted_") {
|
||||
// we need a trick to display Ampersand "&" in stead of "&" or "&"
|
||||
// after translation
|
||||
var txt="<%:Please [Save & Apply] your changes first%>";
|
||||
alert( txt.replace(new RegExp("<%:&%>", "g"), "&") );
|
||||
} else {
|
||||
// should have data because status changed
|
||||
// so update screen
|
||||
if (data) { _data2elements(data); }
|
||||
}
|
||||
// make me invisible
|
||||
obj.parentNode.style.display = "none";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// force to immediate show status on page load (not waiting for XHR.poll)
|
||||
XHR.get('<%=url([[admin]], [[services]], [[ddns]], [[status]])%>', null,
|
||||
function(x, data) {
|
||||
if (data) { _data2elements(data); }
|
||||
}
|
||||
);
|
||||
|
||||
// define only ONE XHR.poll in a page because if one is running it blocks the other one
|
||||
// optimum is to define on Map or Section Level from here you can reach all elements
|
||||
// we need update every 15 seconds only
|
||||
XHR.poll(-1, '<%=url([[admin]], [[services]], [[ddns]], [[status]])%>', null,
|
||||
function(x, data) {
|
||||
if (data) { _data2elements(data); }
|
||||
}
|
||||
);
|
||||
|
||||
//]]></script>
|
||||
|
||||
<fieldset class="cbi-section" style="display:none">
|
||||
<legend id="cbi-ddns-overview-status-legend"><%:Applying changes%></legend>
|
||||
<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" />
|
||||
<span id="cbi-ddns-overview-status-text"><%:Waiting for changes to be applied...%></span>
|
||||
</fieldset>
|
||||
<!-- ++ END ++ Dynamic DNS ++ overview_status.htm ++ -->
|
|
@ -1,60 +0,0 @@
|
|||
<!-- ++ BEGIN ++ Dynamic DNS ++ system_status.htm ++ -->
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
|
||||
//start polling data every 30 second, this doesn't change so much
|
||||
XHR.poll(30, '<%=url([[admin]], [[services]], [[ddns]], [[status]])%>', null,
|
||||
function(x, data) {
|
||||
if (data) {
|
||||
var tbl = document.getElementById('ddns_status_table');
|
||||
// security check
|
||||
if ( !(tbl) ) { return; }
|
||||
|
||||
var rows = [];
|
||||
|
||||
// DDNS Service disabled
|
||||
if (data[0].enabled == 0) {
|
||||
var ddns_legend = document.getElementById('ddns_status_legend');
|
||||
ddns_legend.style.display='none';
|
||||
rows.push([
|
||||
'<strong><font color="red"><%:DDNS Autostart disabled%></font>',
|
||||
'<a class="cbi-button cbi-button-action important" type="button" href="' + data[0].url_up + '"><%:enable here%></a></strong>'
|
||||
]);
|
||||
} else {
|
||||
for(var j = 1; j < data.length; j++ )
|
||||
{
|
||||
|
||||
rows.push([
|
||||
'<strong>' + data[j].section + '</strong>', //configuration
|
||||
data[j].datenextstat ? '<em>'+data[j].datenextstat+'</em>' : '<em>'+data[j].datenext+'</em>',
|
||||
data[j].lookup ? data[j].lookup : '<em><%:config error%></em>',
|
||||
data[j].reg_ip ? data[j].reg_ip : '<em><%:No data%></em>',
|
||||
data[j].iface // monitored interface
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
cbi_update_table(tbl, rows, '<%:There is no service configured.%>');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
//]]></script>
|
||||
|
||||
<fieldset class="cbi-section" id="ddns_status_section">
|
||||
<legend><a href="<%=url([[admin]], [[services]], [[ddns]])%>"><%:Dynamic DNS%></a></legend>
|
||||
<div class="cbi-section-node">
|
||||
<div class="table" id="ddns_status_table">
|
||||
<div class="tr table-titles" id="ddns_status_legend">
|
||||
<div class="th"><%:Configuration%></div>
|
||||
<div class="th"><%:Next Update%></div>
|
||||
<div class="th"><%:Lookup Hostname%></div>
|
||||
<div class="th"><%:Registered IP%></div>
|
||||
<div class="th"><%:Network%></div>
|
||||
</div>
|
||||
<div class="tr">
|
||||
<div class="td"><em><br /><%:Collecting data...%></em></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<!-- ++ END ++ Dynamic DNS ++ system_status.htm ++ -->
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
321
applications/luci-app-ddns/root/usr/libexec/rpcd/luci.ddns
Normal file
321
applications/luci-app-ddns/root/usr/libexec/rpcd/luci.ddns
Normal file
|
@ -0,0 +1,321 @@
|
|||
#!/usr/bin/env lua
|
||||
|
||||
local json = require "luci.jsonc"
|
||||
local nixio = require "nixio"
|
||||
local fs = require "nixio.fs"
|
||||
local UCI = require "luci.model.uci"
|
||||
local sys = require "luci.sys"
|
||||
local util = require "luci.util"
|
||||
|
||||
local luci_helper = "/usr/lib/ddns/dynamic_dns_lucihelper.sh"
|
||||
local srv_name = "ddns-scripts"
|
||||
|
||||
-- convert epoch date to given format
|
||||
local function epoch2date(epoch, format)
|
||||
if not format or #format < 2 then
|
||||
local uci = UCI.cursor()
|
||||
format = uci:get("ddns", "global", "ddns_dateformat") or "%F %R"
|
||||
uci:unload("ddns")
|
||||
end
|
||||
format = format:gsub("%%n", "<br />") -- replace newline
|
||||
format = format:gsub("%%t", " ") -- replace tab
|
||||
return os.date(format, epoch)
|
||||
end
|
||||
|
||||
-- function to calculate seconds from given interval and unit
|
||||
local function calc_seconds(interval, unit)
|
||||
if not tonumber(interval) then
|
||||
return nil
|
||||
elseif unit == "days" then
|
||||
return (tonumber(interval) * 86400) -- 60 sec * 60 min * 24 h
|
||||
elseif unit == "hours" then
|
||||
return (tonumber(interval) * 3600) -- 60 sec * 60 min
|
||||
elseif unit == "minutes" then
|
||||
return (tonumber(interval) * 60) -- 60 sec
|
||||
elseif unit == "seconds" then
|
||||
return tonumber(interval)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local methods = {
|
||||
get_services_log = {
|
||||
args = { service_name = "service_name" },
|
||||
call = function(args)
|
||||
local result = "File not found or empty"
|
||||
local uci = UCI.cursor()
|
||||
|
||||
local dirlog = uci:get('ddns', 'global', 'ddns_logdir') or "/var/log/ddns"
|
||||
|
||||
-- Fallback to default logdir with unsecure path
|
||||
if dirlog:match('%.%.%/') then dirlog = "/var/log/ddns" end
|
||||
|
||||
if args and args.service_name and fs.access("%s/%s.log" % { dirlog, args.service_name }) then
|
||||
result = fs.readfile("%s/%s.log" % { dirlog, args.service_name })
|
||||
end
|
||||
|
||||
uci.unload()
|
||||
|
||||
return { result = result }
|
||||
end
|
||||
},
|
||||
get_services_status = {
|
||||
call = function()
|
||||
local uci = UCI.cursor()
|
||||
|
||||
local rundir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
|
||||
local date_format = uci:get("ddns", "global", "ddns_dateformat")
|
||||
local res = {}
|
||||
|
||||
uci:foreach("ddns", "service", function (s)
|
||||
local ip, last_update, next_update
|
||||
local section = s[".name"]
|
||||
if fs.access("%s/%s.ip" % { rundir, section }) then
|
||||
ip = fs.readfile("%s/%s.ip" % { rundir, section })
|
||||
else
|
||||
local dnsserver = s["dns_server"] or ""
|
||||
local force_ipversion = tonumber(s["force_ipversion"] or 0)
|
||||
local force_dnstcp = tonumber(s["force_dnstcp"] or 0)
|
||||
local is_glue = tonumber(s["is_glue"] or 0)
|
||||
local command = { luci_helper , [[ -]] }
|
||||
local lookup_host = s["lookup_host"] or "_nolookup_"
|
||||
|
||||
if (use_ipv6 == 1) then command[#command+1] = [[6]] end
|
||||
if (force_ipversion == 1) then command[#command+1] = [[f]] end
|
||||
if (force_dnstcp == 1) then command[#command+1] = [[t]] end
|
||||
if (is_glue == 1) then command[#command+1] = [[g]] end
|
||||
command[#command+1] = [[l ]]
|
||||
command[#command+1] = lookup_host
|
||||
command[#command+1] = [[ -S ]]
|
||||
command[#command+1] = section
|
||||
if (#dnsserver > 0) then command[#command+1] = [[ -d ]] .. dnsserver end
|
||||
command[#command+1] = [[ -- get_registered_ip]]
|
||||
line = util.exec(table.concat(command))
|
||||
end
|
||||
|
||||
local last_update = tonumber(fs.readfile("%s/%s.update" % { rundir, section } ) or 0)
|
||||
local next_update, converted_last_update
|
||||
local pid = tonumber(fs.readfile("%s/%s.pid" % { rundir, section } ) or 0)
|
||||
|
||||
if pid > 0 and not nixio.kill(pid, 0) then
|
||||
pid = 0
|
||||
end
|
||||
|
||||
local uptime = sys.uptime()
|
||||
|
||||
local force_seconds = calc_seconds(
|
||||
tonumber(s["force_interval"]) or 72,
|
||||
s["force_unit"] or "hours" )
|
||||
|
||||
-- process running but update needs to happen
|
||||
-- problems if force_seconds > uptime
|
||||
force_seconds = (force_seconds > uptime) and uptime or force_seconds
|
||||
|
||||
if last_update > 0 then
|
||||
local epoch = os.time() - uptime + last_update + force_seconds
|
||||
-- use linux date to convert epoch
|
||||
converted_last_update = epoch2date(epoch,date_format)
|
||||
next_update = epoch2date(epoch + force_seconds)
|
||||
end
|
||||
|
||||
if pid > 0 and ( last_update + force_seconds - uptime ) <= 0 then
|
||||
next_update = "Verify"
|
||||
|
||||
-- run once
|
||||
elseif force_seconds == 0 then
|
||||
next_update = "Run once"
|
||||
|
||||
-- no process running and NOT enabled
|
||||
elseif pid == 0 and s['enabled'] == '0' then
|
||||
next_update = "Disabled"
|
||||
|
||||
-- no process running and enabled
|
||||
elseif pid == 0 and s['enabled'] ~= '0' then
|
||||
next_update = "Stopped"
|
||||
end
|
||||
|
||||
res[section] = {
|
||||
ip = ip and ip:gsub("\n","") or nil,
|
||||
last_update = last_update ~= 0 and converted_last_update or nil,
|
||||
next_update = next_update or nil,
|
||||
pid = pid or nil,
|
||||
}
|
||||
end
|
||||
)
|
||||
|
||||
uci:unload("ddns")
|
||||
|
||||
return res
|
||||
|
||||
end
|
||||
},
|
||||
get_ddns_state = {
|
||||
call = function()
|
||||
local ipkg = require "luci.model.ipkg"
|
||||
local uci = UCI.cursor()
|
||||
local dateformat = uci:get("ddns", "global", "ddns_dateformat") or "%F %R"
|
||||
uci:unload("ddns")
|
||||
local ver, srv_ver_cmd
|
||||
local res = {}
|
||||
|
||||
if ipkg then
|
||||
ver = ipkg.info(srv_name)[srv_name].Version
|
||||
else
|
||||
srv_ver_cmd = luci_helper .. " -V | awk {'print $2'} "
|
||||
ver = util.exec(srv_ver_cmd)
|
||||
end
|
||||
|
||||
res['_version'] = ver and #ver > 0 and ver or nil
|
||||
res['_enabled'] = sys.init.enabled("ddns")
|
||||
res['_curr_dateformat'] = os.date(dateformat)
|
||||
|
||||
return res
|
||||
end
|
||||
},
|
||||
get_env = {
|
||||
call = function()
|
||||
local res = {}
|
||||
local cache = {}
|
||||
|
||||
local function has_wget()
|
||||
return (sys.call( [[which wget >/dev/null 2>&1]] ) == 0)
|
||||
end
|
||||
|
||||
local function has_wgetssl()
|
||||
if cache['has_wgetssl'] then return cache['has_wgetssl'] end
|
||||
local res = (sys.call( [[which wget-ssl >/dev/null 2>&1]] ) == 0)
|
||||
cache['has_wgetssl'] = res
|
||||
return res
|
||||
end
|
||||
|
||||
local function has_curlssl()
|
||||
return (sys.call( [[$(which curl) -V 2>&1 | grep -qF "https"]] ) == 0)
|
||||
end
|
||||
|
||||
local function has_fetch()
|
||||
if cache['has_fetch'] then return cache['has_fetch'] end
|
||||
local res = (sys.call( [[which uclient-fetch >/dev/null 2>&1]] ) == 0)
|
||||
cache['has_fetch'] = res
|
||||
return res
|
||||
end
|
||||
|
||||
local function has_fetchssl()
|
||||
return fs.access("/lib/libustream-ssl.so")
|
||||
end
|
||||
|
||||
local function has_curl()
|
||||
if cache['has_curl'] then return cache['has_curl'] end
|
||||
local res = (sys.call( [[which curl >/dev/null 2>&1]] ) == 0)
|
||||
cache['has_curl'] = res
|
||||
return res
|
||||
end
|
||||
|
||||
local function has_curlpxy()
|
||||
return (sys.call( [[grep -i "all_proxy" /usr/lib/libcurl.so* >/dev/null 2>&1]] ) == 0)
|
||||
end
|
||||
|
||||
local function has_bbwget()
|
||||
return (sys.call( [[$(which wget) -V 2>&1 | grep -iqF "busybox"]] ) == 0)
|
||||
end
|
||||
|
||||
res['has_wget'] = has_wget() or false
|
||||
res['has_curl'] = has_curl() or false
|
||||
|
||||
res['has_ssl'] = has_wgetssl() or has_curlssl() or (has_fetch() and has_fetchssl()) or false
|
||||
res['has_proxy'] = has_wgetssl() or has_curlpxy() or has_fetch() or has_bbwget or false
|
||||
res['has_forceip'] = has_wgetssl() or has_curl() or has_fetch() or false
|
||||
res['has_bindnet'] = has_curl() or has_wgetssl() or false
|
||||
|
||||
local function has_bindhost()
|
||||
if (sys.call( [[which host >/dev/null 2>&1]] ) == 0) then return true end
|
||||
if (sys.call( [[which khost >/dev/null 2>&1]] ) == 0) then return true end
|
||||
if (sys.call( [[which drill >/dev/null 2>&1]] ) == 0) then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
local function has_hostip()
|
||||
return (sys.call( [[which hostip >/dev/null 2>&1]] ) == 0)
|
||||
end
|
||||
|
||||
local function has_nslookup()
|
||||
return (sys.call( [[which nslookup >/dev/null 2>&1]] ) == 0)
|
||||
end
|
||||
|
||||
res['has_dnsserver'] = has_bindhost() or has_hostip() or has_nslookup() or false
|
||||
|
||||
local function check_certs()
|
||||
local _, v = fs.glob("/etc/ssl/certs/*.crt")
|
||||
if ( v == 0 ) then _, v = NXFS.glob("/etc/ssl/certs/*.pem") end
|
||||
return (v > 0)
|
||||
end
|
||||
|
||||
res['has_cacerts'] = check_certs() or false
|
||||
|
||||
res['has_ipv6'] = (fs.access("/proc/net/ipv6_route") and fs.access("/usr/sbin/ip6tables"))
|
||||
|
||||
return res
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
local function parseInput()
|
||||
local parse = json.new()
|
||||
local done, err
|
||||
|
||||
while true do
|
||||
local chunk = io.read(4096)
|
||||
if not chunk then
|
||||
break
|
||||
elseif not done and not err then
|
||||
done, err = parse:parse(chunk)
|
||||
end
|
||||
end
|
||||
|
||||
if not done then
|
||||
print(json.stringify({ error = err or "Incomplete input" }))
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
return parse:get()
|
||||
end
|
||||
|
||||
local function validateArgs(func, uargs)
|
||||
local method = methods[func]
|
||||
if not method then
|
||||
print(json.stringify({ error = "Method not found" }))
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
if type(uargs) ~= "table" then
|
||||
print(json.stringify({ error = "Invalid arguments" }))
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
uargs.ubus_rpc_session = nil
|
||||
|
||||
local k, v
|
||||
local margs = method.args or {}
|
||||
for k, v in pairs(uargs) do
|
||||
if margs[k] == nil or
|
||||
(v ~= nil and type(v) ~= type(margs[k]))
|
||||
then
|
||||
print(json.stringify({ error = "Invalid arguments" }))
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
return method
|
||||
end
|
||||
|
||||
if arg[1] == "list" then
|
||||
local _, method, rv = nil, nil, {}
|
||||
for _, method in pairs(methods) do rv[_] = method.args or {} end
|
||||
print((json.stringify(rv):gsub(":%[%]", ":{}")))
|
||||
elseif arg[1] == "call" then
|
||||
local args = parseInput()
|
||||
local method = validateArgs(arg[2], args)
|
||||
local result, code = method.call(args)
|
||||
print((json.stringify(result):gsub("^%[%]$", "{}")))
|
||||
os.exit(code or 0)
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"luci-app-ddns": {
|
||||
"description": "Grant access to ddns procedures",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.ddns": [ "get_services_status", "get_ddns_state", "get_env", "get_services_log" ],
|
||||
"luci": [ "setInitAction" ],
|
||||
},
|
||||
"file": {
|
||||
"/etc/ddns/services": [ "read" ],
|
||||
"/etc/ddns/services_ipv6": [ "read" ],
|
||||
"/usr/lib/ddns/dynamic_dns_lucihelper.sh": [ "exec" ]
|
||||
},
|
||||
"uci": [ "ddns" ]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue