luci-app-ddns: convert to client side implementation
Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
This commit is contained in:
parent
0731f7e5e4
commit
34fa5122f9
21 changed files with 1380 additions and 3222 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", "")
|
||||
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
|
||||
entry( {"admin", "services", "ddns"}, view("ddns/overview"), _("Dynamic DNS"), 59)
|
||||
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 ++ -->
|
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