mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-09-10 18:34:09 +08:00
update 2026-07-28 01:38:11
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021-2026 sirpdboy <herboy2008@gmail.com>
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-ddns-go
|
||||
PKG_VERSION:=1.6.8
|
||||
PKG_RELEASE:=20260323
|
||||
|
||||
PKG_MAINTAINER:=sirpdboy <herboy2008@gmail.com>
|
||||
PKG_CONFIG_DEPENDS:=
|
||||
|
||||
LUCI_TITLE:=LuCI Support for Dynamic ddns-go Client
|
||||
LUCI_DEPENDS:=+ddns-go
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -0,0 +1,388 @@
|
||||
/* Copyright (C) 2022-2026 sirpdboy herboy2008@gmail.com*/
|
||||
'use strict';
|
||||
'require view';
|
||||
'require fs';
|
||||
'require ui';
|
||||
'require uci';
|
||||
'require form';
|
||||
'require poll';
|
||||
'require rpc';
|
||||
|
||||
const getDDNSGoInfo = rpc.declare({
|
||||
object: 'luci.ddns-go',
|
||||
method: 'get_ver',
|
||||
expect: { 'ver': {} }
|
||||
});
|
||||
|
||||
const getUpdateInfo = rpc.declare({
|
||||
object: 'luci.ddns-go',
|
||||
method: 'last_update',
|
||||
expect: { 'update': {} }
|
||||
});
|
||||
|
||||
const updateMessageMap = {
|
||||
'Already the latest version': _('Already the latest version'),
|
||||
'New version available': _('New version available'),
|
||||
'Update successful': _('Update successful'),
|
||||
'Download update failed': _('Download update failed'),
|
||||
'Update check failed': _('Update check failed'),
|
||||
'Update status unknown': _('Update status unknown')
|
||||
};
|
||||
|
||||
async function checkProcess() {
|
||||
try {
|
||||
const pidofRes = await fs.exec('/bin/pidof', ['ddns-go']);
|
||||
if (pidofRes.code === 0) {
|
||||
return {
|
||||
running: true,
|
||||
pid: pidofRes.stdout.trim()
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
try {
|
||||
const psRes = await fs.exec('/bin/ps', ['-C', 'ddns-go', '-o', 'pid=']);
|
||||
const pid = psRes.stdout.trim();
|
||||
return {
|
||||
running: pid !== '',
|
||||
pid: pid || null
|
||||
};
|
||||
} catch (err) {
|
||||
return { running: false, pid: null };
|
||||
}
|
||||
}
|
||||
|
||||
function getVersionInfo() {
|
||||
return L.resolveDefault(getDDNSGoInfo(), {}).then(function(result) {
|
||||
return result || {};
|
||||
}).catch(function(error) {
|
||||
console.error('Failed to get version:', error);
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
function checkUpdateStatus() {
|
||||
return L.resolveDefault(getUpdateInfo(), {}).then(function(result) {
|
||||
return result || {};
|
||||
}).catch(function(error) {
|
||||
console.error('Failed to get update info:', error);
|
||||
return {};
|
||||
});
|
||||
}
|
||||
function extractPortNumber(portValue) {
|
||||
if (!portValue) return '9876';
|
||||
if (portValue.includes(':')) {
|
||||
var parts = portValue.split(':');
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
return portValue;
|
||||
}
|
||||
|
||||
function renderStatus(isRunning, listen_port, noweb, version) {
|
||||
var statusText = isRunning ? _('RUNNING') : _('NOT RUNNING');
|
||||
var color = isRunning ? 'green' : 'red';
|
||||
var icon = isRunning ? '✓' : '✗';
|
||||
var versionText = version ? `v${version}` : '';
|
||||
|
||||
var html = String.format(
|
||||
'<em><span style="color:%s">%s <strong>%s %s - %s</strong></span></em>',
|
||||
color, icon, _('DDNS-Go'), versionText, statusText
|
||||
);
|
||||
|
||||
if (isRunning) {
|
||||
html += String.format(' <a class="btn cbi-button" href="http://%s:%s" target="_blank">%s</a>',
|
||||
window.location.hostname, listen_port, _('Open Web Interface'));
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderUpdateStatus(updateInfo) {
|
||||
if (!updateInfo || !updateInfo.status) {
|
||||
return '<span style="color:orange"> ⚠ ' + _('Update status unknown') + '</span>';
|
||||
}
|
||||
|
||||
var status = updateInfo.status;
|
||||
var message = updateInfo.message || '';
|
||||
|
||||
for (let [en, zh] of Object.entries(updateMessageMap)) {
|
||||
if (message.includes(en)) {
|
||||
message = message.replace(en, zh);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch(status) {
|
||||
case 'updated':
|
||||
return String.format('<span style="color:green">✓ %s</span>', message);
|
||||
case 'update_available':
|
||||
return String.format('<span style="color:blue">↻ %s</span>', message);
|
||||
case 'latest':
|
||||
return String.format('<span style="color:green">✓ %s</span>', message);
|
||||
case 'download_failed':
|
||||
case 'check_failed':
|
||||
return String.format('<span style="color:red">✗ %s</span>', message);
|
||||
default:
|
||||
return String.format('<span style="color:orange">? %s</span>', message);
|
||||
}
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
uci.load('ddns-go')
|
||||
]);
|
||||
},
|
||||
|
||||
handleResetPassword: async function () {
|
||||
try {
|
||||
ui.showModal(_('Resetting Password'), [
|
||||
E('p', { 'class': 'spinning' }, _('Resetting admin username and password, please wait...'))
|
||||
]);
|
||||
const result = await fs.exec('/usr/bin/ddns-go', ['-resetPassword', 'admin12345', '-c', '/etc/ddns-go/ddns-go-config.yaml']);
|
||||
const configFile = '/etc/ddns-go/ddns-go-config.yaml';
|
||||
const readResult = await fs.read(configFile);
|
||||
if (readResult && readResult.trim() !== '') {
|
||||
let configContent = readResult;
|
||||
configContent = configContent.replace(/(username:\s*).*/g, '$1admin');
|
||||
|
||||
if (!configContent.includes('user:')) {
|
||||
configContent += '\nuser:\n username: admin\n password: $2a$10$G1xO1cVUYtSpPYwV/Jk3l.u7PxLUxo03wntWG6VA9BxAftNWfZEhK';
|
||||
}
|
||||
|
||||
await fs.write(configFile, configContent);
|
||||
}
|
||||
|
||||
ui.hideModal();
|
||||
|
||||
if (result.code === 0) {
|
||||
ui.showModal(_('Username and Password Reset Successful'), [
|
||||
E('p', _('Username: admin, Password: admin12345')),
|
||||
E('p', _('You need to restart DDNS-Go service for the changes to take effect.')),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-positive',
|
||||
'click': ui.createHandlerFn(this, function() {
|
||||
ui.hideModal();
|
||||
this.handleRestartService();
|
||||
})
|
||||
}, _('Restart Service Now')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-neutral',
|
||||
'click': ui.hideModal
|
||||
}, _('Restart Later'))
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
ui.showModal(_('Partial Reset'), [
|
||||
E('p', _('DDNS-Go command reset may have failed, but configuration file has been updated.')),
|
||||
E('p', _('Username: admin, Password: admin12345')),
|
||||
E('p', _('You may need to restart DDNS-Go service manually.')),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-positive',
|
||||
'click': ui.createHandlerFn(this, function() {
|
||||
ui.hideModal();
|
||||
this.handleRestartService();
|
||||
})
|
||||
}, _('Restart Service Now')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-neutral',
|
||||
'click': ui.hideModal
|
||||
}, _('Close'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
ui.hideModal();
|
||||
alert(_('ERROR:') + '\n' + _('Reset username/password failed:') + '\n' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
handleRestartService: async function() {
|
||||
try {
|
||||
// const enabledValue = document.querySelector('input[name="cfg001c48.enabled"]')?.checked ? '1' : '0';
|
||||
const enabledValue = document.querySelectorAll('input[id="widget.cbid.ddns-go.config.enabled"]')?.checked ? '1' : '0';
|
||||
|
||||
await uci.set('ddns-go', 'config', 'enabled', enabledValue);
|
||||
await uci.save('ddns-go');
|
||||
await uci.apply();
|
||||
|
||||
await fs.exec('/etc/init.d/ddns-go', ['stop']);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
if (enabledValue === '1') {
|
||||
await fs.exec('/etc/init.d/ddns-go', ['start']);
|
||||
}
|
||||
|
||||
alert(_('SUCCESS:') + '\n' + _('DDNS-Go service restarted successfully'));
|
||||
if (window.statusPoll) {
|
||||
window.statusPoll();
|
||||
}
|
||||
} catch (error) {
|
||||
alert(_('ERROR:') + '\n' + _('Failed to restart service:') + '\n' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
handleUpdate: async function () {
|
||||
try {
|
||||
var updateView = document.getElementById('update_status');
|
||||
if (updateView) {
|
||||
updateView.innerHTML = '<span class="spinning"></span> ' + _('Updating, please wait...');
|
||||
}
|
||||
const updateInfo = await checkUpdateStatus();
|
||||
if (updateView) {
|
||||
updateView.innerHTML = renderUpdateStatus(updateInfo);
|
||||
}
|
||||
|
||||
if (updateInfo.update_successful || updateInfo.status === 'updated') {
|
||||
if (window.statusPoll) {
|
||||
window.statusPoll();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
var updateView = document.getElementById('update_status');
|
||||
if (updateView) {
|
||||
getVersionInfo().then(function(versionInfo) {
|
||||
var version = versionInfo.version || '';
|
||||
updateView.innerHTML = String.format('<span style="color:green">✓ %s v%s</span>',
|
||||
_('Current Version'), version);
|
||||
});
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Update failed:', error);
|
||||
var updateView = document.getElementById('update_status');
|
||||
if (updateView) {
|
||||
updateView.innerHTML = '<span style="color:red">✗ ' + _('Update failed') + '</span>';
|
||||
|
||||
setTimeout(() => {
|
||||
getVersionInfo().then(function(versionInfo) {
|
||||
var version = versionInfo.version || '';
|
||||
updateView.innerHTML = String.format('<span>%s v%s</span>',
|
||||
_('Current Version'), version);
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var m, s, o;
|
||||
|
||||
var portValue = uci.get('ddns-go', 'config', 'port') || '[::]:9876';
|
||||
var listen_port = extractPortNumber(portValue);
|
||||
var noweb = uci.get('ddns-go', 'config', 'noweb') || '0';
|
||||
|
||||
m = new form.Map('ddns-go', _('DDNS-GO'),
|
||||
_('DDNS-GO automatically obtains your public IPv4 or IPv6 address and resolves it to the corresponding domain name service.'));
|
||||
|
||||
s = m.section(form.TypedSection);
|
||||
s.anonymous = true;
|
||||
|
||||
s.render = function() {
|
||||
var statusView = E('p', { id: 'control_status' },
|
||||
'<span class="spinning"></span> ' + _('Checking status...'));
|
||||
|
||||
window.statusPoll = function() {
|
||||
return Promise.all([
|
||||
checkProcess(),
|
||||
getVersionInfo()
|
||||
]).then(function(results) {
|
||||
var [processInfo, versionInfo] = results;
|
||||
var version = versionInfo.version || '';
|
||||
statusView.innerHTML = renderStatus(processInfo.running, listen_port, noweb, version);
|
||||
}).catch(function(err) {
|
||||
console.error('Status check failed:', err);
|
||||
statusView.innerHTML = '<span style="color:orange">⚠ ' + _('Status check error') + '</span>';
|
||||
});
|
||||
};
|
||||
|
||||
poll.add(window.statusPoll, 5);
|
||||
|
||||
return E('div', { class: 'cbi-section', id: 'status_bar' }, [
|
||||
statusView
|
||||
]);
|
||||
};
|
||||
|
||||
s = m.section(form.NamedSection, 'config', 'basic');
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable'));
|
||||
o.default = o.disabled;
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.Value, 'port', _('Listen port'));
|
||||
o.default = '9876';
|
||||
o.rmempty = false;
|
||||
o.datatype = 'string';
|
||||
o.description = _('Port number (1-65535)');
|
||||
|
||||
o = s.option(form.Value, 'time', _('Update interval (seconds)'));
|
||||
o.default = '300';
|
||||
o.datatype = 'range(60,86400)';
|
||||
o.description = _('Update interval in seconds (60-86400)');
|
||||
|
||||
o = s.option(form.Value, 'ctimes', _('Provider comparison interval'));
|
||||
o.default = '5';
|
||||
o.datatype = 'range(1,60)';
|
||||
o.description = _('Number of times to compare with service provider (1-60)');
|
||||
|
||||
o = s.option(form.Value, 'skipverify', _('Skip verifying certificates'));
|
||||
o.default = '0';
|
||||
o.value('0', _('No'));
|
||||
o.value('1', _('Yes'));
|
||||
|
||||
o = s.option(form.Value, 'dns', _('Specify DNS resolution server'));
|
||||
o.value('223.5.5.5', _('Ali DNS 223.5.5.5'));
|
||||
o.value('223.6.6.6', _('Ali DNS 223.6.6.6'));
|
||||
o.value('119.29.29.29', _('Tencent DNS 119.29.29.29'));
|
||||
o.value('1.1.1.1', _('CloudFlare DNS 1.1.1.1'));
|
||||
o.value('8.8.8.8', _('Google DNS 8.8.8.8'));
|
||||
o.value('8.8.4.4', _('Google DNS 8.8.4.4'));
|
||||
o.datatype = 'ipaddr';
|
||||
|
||||
o = s.option(form.Flag, 'noweb', _('Do not start web services'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.Value, 'delay', _('Delayed Start (seconds)'));
|
||||
o.default = '60';
|
||||
|
||||
o = s.option(form.Button, '_newpassword', _('Reset account password'));
|
||||
o.inputtitle = _('Reset');
|
||||
o.inputstyle = 'apply';
|
||||
o.onclick = L.bind(this.handleResetPassword, this, data);
|
||||
|
||||
o = s.option(form.Button, '_update', _('Check Update'));
|
||||
o.inputtitle = _('Check');
|
||||
o.inputstyle = 'apply';
|
||||
o.onclick = L.bind(this.handleUpdate, this, data);
|
||||
|
||||
o = s.option(form.DummyValue, '_update_status', _('Current Version'));
|
||||
o.rawhtml = true;
|
||||
|
||||
var currentVersion = '';
|
||||
|
||||
getVersionInfo().then(function(versionInfo) {
|
||||
currentVersion = versionInfo.version || '';
|
||||
var updateView = document.getElementById('update_status');
|
||||
if (updateView) {
|
||||
updateView.innerHTML = String.format('<span>v%s</span>', currentVersion);
|
||||
}
|
||||
});
|
||||
|
||||
o.cfgvalue = function() {
|
||||
return E('div', { style: 'margin: 5px 0;' }, [
|
||||
E('span', { id: 'update_status' },
|
||||
currentVersion ? String.format('v%s', currentVersion) : _('Loading...'))
|
||||
]);
|
||||
};
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
/* Copyright (C) 2022-2026 sirpdboy herboy2008@gmail.com */
|
||||
|
||||
'use strict';
|
||||
'require view';
|
||||
'require fs';
|
||||
'require ui';
|
||||
'require uci';
|
||||
'require form';
|
||||
'require poll';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return uci.load('ddns-go');
|
||||
},
|
||||
|
||||
checkRunning: function() {
|
||||
return fs.exec('/bin/pidof', ['ddns-go']).then(function(pidRes) {
|
||||
if (pidRes.code === 0) return { isRunning: true };
|
||||
return fs.exec('/bin/ash', ['-c', 'ps | grep -q "[d]dns-go"']).then(function(grepRes) {
|
||||
return { isRunning: grepRes.code === 0 };
|
||||
});
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var self = this;
|
||||
|
||||
return this.checkRunning().then(function(checkResult) {
|
||||
var isRunning = checkResult.isRunning;
|
||||
var port = uci.get('ddns-go', 'config', 'port') || '[::]:9876';
|
||||
var noweb = uci.get('ddns-go', 'config', 'noweb');
|
||||
port = port.split(':').pop();
|
||||
|
||||
var container = E('div');
|
||||
if (!isRunning || noweb === '1') {
|
||||
if (!isRunning) {
|
||||
var message = _('DDNS-GO Service Not Running');
|
||||
}
|
||||
if (noweb === '1') {
|
||||
var message = _('DDNS-GO Web Interface Disabled');
|
||||
}
|
||||
|
||||
container.appendChild(E('div', {
|
||||
style: 'text-align: center; padding: 2em;'
|
||||
}, [
|
||||
E('img', {
|
||||
src: 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMjQiIGhlaWdodD0iMTAyNCIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCI+PHBhdGggZmlsbD0iI2RmMDAwMCIgZD0iTTk0Mi40MjEgMjM0LjYyNGw4MC44MTEtODAuODExLTE1My4wNDUtMTUzLjA0NS04MC44MTEgODAuODExYy03OS45NTctNTEuNjI3LTE3NS4xNDctODEuNTc5LTI3Ny4zNzYtODEuNTc5LTI4Mi43NTIgMC01MTIgMjI5LjI0OC01MTIgNTEyIDAgMTAyLjIyOSAyOS45NTIgMTk3LjQxOSA4MS41NzkgMjc3LjM3NmwtODAuODExIDgwLjgxMSAxNTMuMDQ1IDE1My4wNDUgODAuODExLTgwLjgxMWM3OS45NTcgNTEuNjI3IDE3NS4xNDcgODEuNTc5IDI3Ny4zNzYgODEuNTc5IDI4Mi43NTIgMCA1MTItMjI5LjI0OCA1MTItNTEyIDAtMTAyLjIyOS0yOS45NTItMTk3LjQxOS04MS41NzktMjc3LjM3NnpNMTk0Ljk0NCA1MTJjMC0xNzUuMTA0IDE0MS45NTItMzE3LjA1NiAzMTcuMDU2LTMxNy4wNTYgNDggMCA5My40ODMgMTAuNjY3IDEzNC4yMjkgMjkuNzgxbC00MjEuNTQ3IDQyMS41NDdjLTE5LjA3Mi00MC43ODktMjkuNzM5LTg2LjI3Mi0yOS43MzktMTM0LjI3MnpNNTEyIDgyOS4wNTZjLTQ4IDAtOTMuNDgzLTEwLjY2Ny0xMzQuMjI5LTI5Ljc4MWw0MjEuNTQ3LTQyMS41NDdjMTkuMDcyIDQwLjc4OSAyOS43ODEgODYuMjcyIDI5Ljc4MSAxMzQuMjI5LTAuMDQzIDE3NS4xNDctMTQxLjk5NSAzMTcuMDk5LTMxNy4wOTkgMzE3LjA5OXoiLz48L3N2Zz4=',
|
||||
style: 'width: 100px; height: 100px; margin-bottom: 1em;'
|
||||
}),
|
||||
E('h2', {}, message)
|
||||
]));
|
||||
} else {
|
||||
var isHttps = window.location.protocol === 'https:';
|
||||
|
||||
if (isHttps) {
|
||||
var buttonContainer = E('div', {
|
||||
style: 'text-align: center; padding: 2em;'
|
||||
}, [
|
||||
E('h2', {}, _('DDNS-GO Control panel')),
|
||||
E('p', {}, _('Due to browser security policies, the DDNS-GO interface https cannot be embedded directly.')),
|
||||
E('a', {
|
||||
href: 'http://' + window.location.hostname + ':' + port,
|
||||
target: '_blank',
|
||||
class: 'cbi-button cbi-button-apply',
|
||||
style: 'display: inline-block; margin-top: 1em; padding: 10px 20px; font-size: 16px; text-decoration: none; color: white;'
|
||||
}, _('Open Web Interface')),
|
||||
]);
|
||||
container.appendChild(buttonContainer);
|
||||
} else {
|
||||
var iframe = E('iframe', {
|
||||
src: 'http://' + window.location.hostname + ':' + port,
|
||||
style: 'width: 100%; min-height: 100vh; border: none;'
|
||||
});
|
||||
container.appendChild(iframe);
|
||||
}
|
||||
}
|
||||
|
||||
poll.add(function() {
|
||||
return self.checkRunning().then(function(checkResult) {
|
||||
var newStatus = checkResult.isRunning;
|
||||
if (newStatus !== isRunning) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}, 5);
|
||||
|
||||
poll.start();
|
||||
|
||||
return container;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@@ -0,0 +1,258 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
* Copyright (C) 2022-2026 sirpdboy <herboy2008@gmail.com>
|
||||
*/
|
||||
'use strict';
|
||||
'require dom';
|
||||
'require fs';
|
||||
'require poll';
|
||||
'require uci';
|
||||
'require view';
|
||||
'require form';
|
||||
|
||||
return view.extend({
|
||||
render: function () {
|
||||
var css = `
|
||||
.log-container {
|
||||
max-height: 1200px;
|
||||
overflow-y: auto;
|
||||
border-radius: 3px;
|
||||
margin-top: 10px;
|
||||
padding: 5px;
|
||||
background-color: var(--background-color);
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.log-line {
|
||||
padding: 3px 5px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
border-bottom: 1px solid var(--border-color-light);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.log-line:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.log-timestamp {
|
||||
color: #0066cc;
|
||||
margin-right: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.log-error {
|
||||
color: #cc0000;
|
||||
}
|
||||
.log-warning {
|
||||
color: #ff9900;
|
||||
}
|
||||
.control-buttons {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
var log_container = E('div', {
|
||||
'class': 'log-container',
|
||||
'id': 'log_container',
|
||||
'style': 'min-height: 200px;'
|
||||
}, E('div', { 'class': 'log-line' }, _('Loading logs...')));
|
||||
|
||||
|
||||
var lastLogContent = '';
|
||||
var lastScrollTop = 0;
|
||||
var isScrolledToTop = true;
|
||||
|
||||
function extractDDNSGoMessage(line) {
|
||||
if (!line || !line.includes('ddns-go')) return null;
|
||||
|
||||
var regex = /^(.*?ddns-go.*?):\s*(.*)$/;
|
||||
var match = line.match(regex);
|
||||
|
||||
if (match) {
|
||||
var timestampMatch = line.match(/^([A-Z][a-z]{2}\s+[A-Z][a-z]{2}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s+\d{4})/);
|
||||
if (timestampMatch) {
|
||||
return {
|
||||
timestamp: timestampMatch[1],
|
||||
message: match[2]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var selfTimestampMatch = line.match(/(\d{4}\/\d{2}\/\d{2}\s+\d{2}:\d{2}:\d{2})\s+(.*)$/);
|
||||
if (selfTimestampMatch) {
|
||||
return {
|
||||
timestamp: selfTimestampMatch[1],
|
||||
message: selfTimestampMatch[2]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
timestamp: null,
|
||||
message: line
|
||||
};
|
||||
}
|
||||
|
||||
function formatLogLine(line) {
|
||||
if (!line || line.trim() === '') return null;
|
||||
|
||||
var extracted = extractDDNSGoMessage(line);
|
||||
if (!extracted) return null;
|
||||
|
||||
var lineClass = ['log-line'];
|
||||
|
||||
if (line.includes('err') || line.includes('ERROR') || line.includes('failed')) {
|
||||
lineClass.push('log-error');
|
||||
} else if (line.includes('warn') || line.includes('WARNING')) {
|
||||
lineClass.push('log-warning');
|
||||
}
|
||||
|
||||
if (extracted.timestamp) {
|
||||
return E('div', { 'class': lineClass.join(' ') }, [
|
||||
E('span', { 'class': 'log-timestamp' }, extracted.timestamp + ' '),
|
||||
E('span', { 'class': 'log-message' }, extracted.message)
|
||||
]);
|
||||
} else {
|
||||
return E('div', { 'class': lineClass.join(' ') }, extracted.message);
|
||||
}
|
||||
}
|
||||
function formatLogContent(logContent) {
|
||||
if (!logContent || logContent.trim() === '') {
|
||||
return E('div', { 'class': 'log-line' }, _('No ddns-go logs found.'));
|
||||
}
|
||||
|
||||
var lines = logContent.split('\n');
|
||||
var formattedLines = [];
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim();
|
||||
if (line === '' || line.includes('No ddns-go logs found')) continue;
|
||||
|
||||
var formattedLine = formatLogLine(line);
|
||||
if (formattedLine) {
|
||||
formattedLines.push(formattedLine);
|
||||
}
|
||||
}
|
||||
|
||||
if (formattedLines.length === 0) {
|
||||
return E('div', { 'class': 'log-line' }, _('No ddns-go logs found.'));
|
||||
}
|
||||
|
||||
formattedLines.reverse();
|
||||
|
||||
return E('div', {}, formattedLines);
|
||||
}
|
||||
|
||||
function clearLogs(button) {
|
||||
button.disabled = true;
|
||||
button.textContent = _('Clearing...');
|
||||
|
||||
return fs.exec('/usr/libexec/ddns-go-call', ['clear_logs'])
|
||||
.then(function(res) {
|
||||
button.textContent = _('Logs cleared!');
|
||||
lastLogContent = '';
|
||||
return fetchLogs();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Clear logs error:', err);
|
||||
button.textContent = _('Failed to clear');
|
||||
})
|
||||
.finally(function() {
|
||||
setTimeout(function() {
|
||||
button.disabled = false;
|
||||
button.textContent = _('Clear Logs');
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
function fetchLogs() {
|
||||
|
||||
return fs.exec('/usr/libexec/ddns-go-call', ['get_logs'])
|
||||
.then(function(res) {
|
||||
var logContent = '';
|
||||
if (res === null || res === undefined) {
|
||||
logContent = '';
|
||||
} else if (typeof res === 'string') {
|
||||
logContent = res;
|
||||
} else if (res.stdout !== undefined) {
|
||||
logContent = res.stdout;
|
||||
} else if (res.data !== undefined) {
|
||||
logContent = res.data;
|
||||
} else if (typeof res === 'object') {
|
||||
logContent = JSON.stringify(res);
|
||||
}
|
||||
|
||||
logContent = logContent.trim();
|
||||
var lineCount = logContent.split('\n').filter(l =>
|
||||
l.trim() !== '' && !l.includes('No ddns-go logs found')
|
||||
).length;
|
||||
|
||||
if (logContent !== lastLogContent) {
|
||||
|
||||
var formattedLog = formatLogContent(logContent);
|
||||
|
||||
var prevScrollHeight = log_container.scrollHeight;
|
||||
var prevScrollTop = log_container.scrollTop;
|
||||
|
||||
dom.content(log_container, formattedLog);
|
||||
lastLogContent = logContent;
|
||||
|
||||
if (!isScrolledToTop) {
|
||||
var newScrollHeight = log_container.scrollHeight;
|
||||
var heightDiff = newScrollHeight - prevScrollHeight;
|
||||
log_container.scrollTop = prevScrollTop + heightDiff;
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Log fetch error:', err);
|
||||
var errorMsg = _('Failed to read logs: %s').format(err.message || 'Resource not found');
|
||||
dom.content(log_container, E('div', { 'class': 'log-line log-error' }, errorMsg));
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
var clear_button = E('button', {
|
||||
'class': 'cbi-button cbi-button-remove',
|
||||
'click': function(ev) {
|
||||
ev.preventDefault();
|
||||
clearLogs(ev.target);
|
||||
}
|
||||
}, _('Clear Logs'));
|
||||
|
||||
|
||||
log_container.addEventListener('scroll', function() {
|
||||
lastScrollTop = this.scrollTop;
|
||||
isScrolledToTop = this.scrollTop <= 1;
|
||||
});
|
||||
|
||||
setTimeout(fetchLogs, 200);
|
||||
|
||||
poll.add(L.bind(function() {
|
||||
return fetchLogs().catch(function(err) {
|
||||
console.error('Poll error:', err);
|
||||
});
|
||||
}));
|
||||
|
||||
poll.start();
|
||||
|
||||
return E('div', { 'class': 'cbi-map' }, [
|
||||
E('style', [css]),
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('div', { 'class': 'control-buttons' }, [ clear_button]),
|
||||
log_container,
|
||||
E('small', {}, [
|
||||
_('Refresh every 5 seconds.').format(L.env.pollinterval),
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Project-Id-Version: ddns-go\n"
|
||||
|
||||
msgid "DDNS-GO"
|
||||
msgstr ""
|
||||
|
||||
msgid "DDNS-GO automatically obtains your public IPv4 or IPv6 address and resolves it to the corresponding domain name service."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable"
|
||||
msgstr ""
|
||||
|
||||
msgid "Listen port"
|
||||
msgstr ""
|
||||
|
||||
msgid "DDNS-GO Service Not Running"
|
||||
msgstr ""
|
||||
|
||||
msgid "DDNS-GO Web Interface Disabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "DDNS-GO Control panel"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update interval(seconds)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Compare with service provider N times intervals"
|
||||
msgstr ""
|
||||
|
||||
msgid "Skip verifying certificates"
|
||||
msgstr ""
|
||||
|
||||
msgid "Specify DNS resolution server"
|
||||
msgstr ""
|
||||
|
||||
msgid "Do not start web services"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delayed Start (seconds)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reset account password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check Update"
|
||||
msgstr ""
|
||||
|
||||
msgid "Current Version:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
msgid "RUNNING"
|
||||
msgstr ""
|
||||
|
||||
msgid "NOT RUNNING"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open Web Interface"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checking status..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Status check error"
|
||||
msgstr ""
|
||||
|
||||
msgid "Already the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "New version available"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update successful"
|
||||
msgstr ""
|
||||
|
||||
msgid "Download update failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update check failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update status unknown"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Updating, please wait..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Unknown"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to load"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log is clean."
|
||||
msgstr ""
|
||||
|
||||
msgid "Clear Logs"
|
||||
msgstr ""
|
||||
|
||||
msgid "Logs cleared!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Refresh every 5 seconds."
|
||||
msgstr ""
|
||||
|
||||
msgid "No ddns-go logs found"
|
||||
msgstr ""
|
||||
|
||||
msgid "Resetting Password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Resetting admin username and password, please wait..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Username and Password Reset Successful"
|
||||
msgstr ""
|
||||
|
||||
msgid "Username: admin, Password: admin12345"
|
||||
msgstr ""
|
||||
|
||||
msgid "You need to restart DDNS-Go service for the changes to take effect."
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart Service Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart Later"
|
||||
msgstr ""
|
||||
|
||||
msgid "Partial Reset"
|
||||
msgstr ""
|
||||
|
||||
msgid "DDNS-Go command reset may have failed, but configuration file has been updated."
|
||||
msgstr ""
|
||||
|
||||
msgid "You may need to restart DDNS-Go service manually."
|
||||
msgstr ""
|
||||
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
msgid "ERROR:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reset username/password failed:"
|
||||
msgstr ""
|
||||
|
||||
msgid "SUCCESS:"
|
||||
msgstr ""
|
||||
|
||||
msgid "DDNS-Go service restarted successfully"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to restart service:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ali DNS 223.5.5.5"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ali DNS 223.6.6.6"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tencent DNS 119.29.29.29"
|
||||
msgstr ""
|
||||
|
||||
msgid "CloudFlare DNS 1.1.1.1"
|
||||
msgstr ""
|
||||
|
||||
msgid "Google DNS 8.8.8.8"
|
||||
msgstr ""
|
||||
|
||||
msgid "Google DNS 8.8.4.4"
|
||||
msgstr ""
|
||||
|
||||
msgid "Found %d ddns-go entries. Last updated: %s"
|
||||
msgstr ""
|
||||
@@ -0,0 +1,199 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Project-Id-Version: ddns-go\n"
|
||||
|
||||
msgid "DDNS-GO"
|
||||
msgstr "DDNS-GO"
|
||||
|
||||
msgid "DDNS-GO automatically obtains your public IPv4 or IPv6 address and resolves it to the corresponding domain name service."
|
||||
msgstr "DDNS-GO 自动获取您的公网 IPv4 或 IPv6 地址,并解析到对应的域名服务。"
|
||||
|
||||
msgid "Enable"
|
||||
msgstr "启用"
|
||||
|
||||
msgid "Listen port"
|
||||
msgstr "监听端口"
|
||||
|
||||
msgid "DDNS-GO Service Not Running"
|
||||
msgstr "DDNS-GO服务未启用"
|
||||
|
||||
msgid "DDNS-GO Web Interface Disabled"
|
||||
msgstr "DDNS-GO WEB服务禁用"
|
||||
|
||||
msgid "DDNS-GO Control panel"
|
||||
msgstr "DDNS-GO操作台"
|
||||
|
||||
msgid "Update interval (seconds)"
|
||||
msgstr "更新间隔(秒)"
|
||||
|
||||
msgid "Provider comparison interval"
|
||||
msgstr "提供商比较间隔"
|
||||
|
||||
msgid "Number of times to compare with service provider (1-60)"
|
||||
msgstr "与服务提供商间隔比较的次数(1-60)"
|
||||
|
||||
msgid "Skip verifying certificates"
|
||||
msgstr "跳过证书验证"
|
||||
|
||||
msgid "Specify DNS resolution server"
|
||||
msgstr "指定DNS解析服务器"
|
||||
|
||||
msgid "Do not start web services"
|
||||
msgstr "不启动Web服务"
|
||||
|
||||
msgid "Delayed Start (seconds)"
|
||||
msgstr "延迟启动(秒)"
|
||||
|
||||
msgid "Reset account password"
|
||||
msgstr "重置账户密码"
|
||||
|
||||
msgid "Check Update"
|
||||
msgstr "检查更新"
|
||||
|
||||
msgid "Port number (1-65535)"
|
||||
msgstr "端口范围(1-65535)"
|
||||
|
||||
msgid "Check"
|
||||
msgstr "检查"
|
||||
|
||||
msgid "Update interval in seconds (60-86400)"
|
||||
msgstr "更新间隔范围(60-86400)秒"
|
||||
|
||||
msgid "Current Version"
|
||||
msgstr "当前版本"
|
||||
|
||||
msgid "Reset"
|
||||
msgstr "重置"
|
||||
|
||||
msgid "RUNNING"
|
||||
msgstr "运行中"
|
||||
|
||||
msgid "NOT RUNNING"
|
||||
msgstr "未运行"
|
||||
|
||||
msgid "Open Web Interface"
|
||||
msgstr "打开Web界面"
|
||||
|
||||
msgid "Checking status..."
|
||||
msgstr "检查状态中..."
|
||||
|
||||
msgid "Status check error"
|
||||
msgstr "状态检查错误"
|
||||
|
||||
msgid "Already the latest version"
|
||||
msgstr "已是最新版本"
|
||||
|
||||
msgid "New version available"
|
||||
msgstr "有新版本可用"
|
||||
|
||||
msgid "Update successful"
|
||||
msgstr "更新成功"
|
||||
|
||||
msgid "Download update failed"
|
||||
msgstr "下载更新失败"
|
||||
|
||||
msgid "Update check failed"
|
||||
msgstr "检查更新失败"
|
||||
|
||||
msgid "Update status unknown"
|
||||
msgstr "更新状态未知"
|
||||
|
||||
msgid "Update failed"
|
||||
msgstr "更新失败"
|
||||
|
||||
msgid "Updating, please wait..."
|
||||
msgstr "正在更新,请稍候..."
|
||||
|
||||
msgid "Loading..."
|
||||
msgstr "加载中..."
|
||||
|
||||
msgid "Unknown"
|
||||
msgstr "未知"
|
||||
|
||||
msgid "Failed to load"
|
||||
msgstr "加载失败"
|
||||
|
||||
msgid "Log is clean."
|
||||
msgstr "日志清除"
|
||||
|
||||
msgid "Clear Logs"
|
||||
msgstr "清除日志"
|
||||
|
||||
msgid "Logs cleared!"
|
||||
msgstr "已清除"
|
||||
|
||||
msgid "Refresh every 5 seconds."
|
||||
msgstr "每 5 秒刷新"
|
||||
|
||||
msgid "No ddns-go logs found"
|
||||
msgstr "没有日志"
|
||||
|
||||
msgid "Resetting Password"
|
||||
msgstr "重置密码"
|
||||
|
||||
msgid "Resetting admin username and password, please wait..."
|
||||
msgstr "正在重置管理员用户名和密码,请稍候..."
|
||||
|
||||
msgid "Username and Password Reset Successful"
|
||||
msgstr "用户名和密码重置成功"
|
||||
|
||||
msgid "Username: admin, Password: admin12345"
|
||||
msgstr "用户名: admin, 密码: admin12345"
|
||||
|
||||
msgid "You need to restart DDNS-Go service for the changes to take effect."
|
||||
msgstr "您需要重启 DDNS-Go 服务以使更改生效。"
|
||||
|
||||
msgid "Restart Service Now"
|
||||
msgstr "立即重启服务"
|
||||
|
||||
msgid "Restart Later"
|
||||
msgstr "稍后重启"
|
||||
|
||||
msgid "Partial Reset"
|
||||
msgstr "部分重置"
|
||||
|
||||
msgid "DDNS-Go command reset may have failed, but configuration file has been updated."
|
||||
msgstr "DDNS-Go 命令重置可能失败,但配置文件已更新。"
|
||||
|
||||
msgid "You may need to restart DDNS-Go service manually."
|
||||
msgstr "您可能需要手动重启 DDNS-Go 服务。"
|
||||
|
||||
msgid "Close"
|
||||
msgstr "关闭"
|
||||
|
||||
msgid "ERROR:"
|
||||
msgstr "错误:"
|
||||
|
||||
msgid "Reset username/password failed:"
|
||||
msgstr "重置用户名/密码失败:"
|
||||
|
||||
msgid "SUCCESS:"
|
||||
msgstr "成功:"
|
||||
|
||||
msgid "DDNS-Go service restarted successfully"
|
||||
msgstr "DDNS-Go 服务重启成功"
|
||||
|
||||
msgid "Failed to restart service:"
|
||||
msgstr "重启服务失败:"
|
||||
|
||||
msgid "Ali DNS 223.5.5.5"
|
||||
msgstr "阿里 DNS 223.5.5.5"
|
||||
|
||||
msgid "Ali DNS 223.6.6.6"
|
||||
msgstr "阿里 DNS 223.6.6.6"
|
||||
|
||||
msgid "Tencent DNS 119.29.29.29"
|
||||
msgstr "腾讯 DNS 119.29.29.29"
|
||||
|
||||
msgid "CloudFlare DNS 1.1.1.1"
|
||||
msgstr "CloudFlare DNS 1.1.1.1"
|
||||
|
||||
msgid "Google DNS 8.8.8.8"
|
||||
msgstr "谷歌 DNS 8.8.8.8"
|
||||
|
||||
msgid "Google DNS 8.8.4.4"
|
||||
msgstr "谷歌 DNS 8.8.4.4"
|
||||
|
||||
msgid "Found %d ddns-go entries. Last updated: %s"
|
||||
msgstr "找到 %d ddns-go记录,最近时间: %s"
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
chmod +x /usr/share/rpcd/ucode/luci.ddns-go
|
||||
chown root:www-data /usr/libexec/ddns-go-call
|
||||
chmod 750 /usr/libexec/ddns-go-call
|
||||
rm -f /tmp/luci-indexcache
|
||||
/etc/init.d/rpcd restart
|
||||
exit 0
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
case "$1" in
|
||||
"get_logs")
|
||||
logread -l 200 2>/dev/null | grep ddns-go || echo "No ddns-go logs found"
|
||||
;;
|
||||
"clear_logs")
|
||||
/etc/init.d/log restart >/dev/null 2>&1
|
||||
echo "Logs cleared"
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"admin/services/ddns-go": {
|
||||
"title": "DDNS-GO",
|
||||
"order": 58,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-ddns-go" ],
|
||||
"uci": { "ddns-go": true }
|
||||
}
|
||||
},
|
||||
|
||||
"admin/services/ddns-go/ddns-go": {
|
||||
"title": "DDNS-GO Control panel",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "ddns-go/ddns-go"
|
||||
}
|
||||
},
|
||||
"admin/services/ddns-go/config": {
|
||||
"title": "Base Setting",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "ddns-go/config"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/services/ddns-go/log": {
|
||||
"title": "Log",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "ddns-go/log"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"luci-app-ddns-go": {
|
||||
"description": "Grant UCI access for luci-app-ddns-go",
|
||||
"read": {
|
||||
"uci": [ "ddns-go" ],
|
||||
"file": {
|
||||
"/etc/init.d/ddns-go": [ "exec" ],
|
||||
"/usr/libexec/ddns-go-call": [ "exec" ],
|
||||
"/usr/share/rpcd/ucode/luci.ddns-go": [ "exec" ],
|
||||
"/bin/pidof": [ "exec" ],
|
||||
"/bin/ps": [ "exec" ],
|
||||
"/bin/ash": [ "exec" ],
|
||||
"/etc/ddns-go/ddns-go-config.yaml": [ "read" ],
|
||||
"/var/log/*": [ "read" ],
|
||||
"/bin/logread": [ "exec" ]
|
||||
},
|
||||
"ubus": {
|
||||
"rc": [ "*" ],
|
||||
"service": [ "list" ],
|
||||
"luci.ddns-go": [ "*" ],
|
||||
"network.interface.*": [ "status" ],
|
||||
"network": [ "*" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "ddns-go" ],
|
||||
"file": {
|
||||
"/etc/ddns-go/ddns-go-config.yaml": [ "write" ]
|
||||
},
|
||||
"ubus": {
|
||||
"luci.ddns-go": [ "*" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/ucode
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* Copyright (C) 2022-2026 sirpdboy <herboy2008@gmail.com>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { access, error, lstat, popen, readfile, writefile } from 'fs';
|
||||
|
||||
/* Kanged from ucode/luci */
|
||||
function shellquote(s) {
|
||||
return `'${replace(s, "'", "'\\''")}'`;
|
||||
}
|
||||
function get_current_version() {
|
||||
if (!access('/usr/bin/ddns-go'))
|
||||
return null;
|
||||
|
||||
const fd = popen('/usr/bin/ddns-go -v');
|
||||
if (fd) {
|
||||
let version_output = fd.read('all');
|
||||
fd.close();
|
||||
|
||||
if (!version_output || length(version_output) === 0)
|
||||
return null;
|
||||
|
||||
try {
|
||||
version_output = replace(trim(version_output), /^v/, '');
|
||||
return version_output;
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const methods = {
|
||||
get_ver: {
|
||||
call: function() {
|
||||
let current_version = get_current_version();
|
||||
if (!current_version)
|
||||
return { ver: {}, error: 'ddns-go not found or version check failed' };
|
||||
|
||||
return { ver: { version: current_version } };
|
||||
}
|
||||
},
|
||||
|
||||
last_update: {
|
||||
call: function() {
|
||||
if (!access('/usr/bin/ddns-go'))
|
||||
return { update: {}, error: 'ddns-go not found' };
|
||||
let version_before = get_current_version();
|
||||
|
||||
const fd = popen('/usr/bin/ddns-go -u');
|
||||
if (fd) {
|
||||
let output = fd.read('all');
|
||||
fd.close();
|
||||
|
||||
if (!output || length(output) === 0)
|
||||
return { update: {}, error: 'empty response' };
|
||||
|
||||
try {
|
||||
output = trim(output);
|
||||
let update_info = {
|
||||
raw_output: output,
|
||||
version_before: version_before,
|
||||
version_after: null,
|
||||
has_update: false,
|
||||
update_successful: false,
|
||||
current_version: '',
|
||||
latest_version: '',
|
||||
status: 'unknown',
|
||||
message: output
|
||||
};
|
||||
|
||||
update_info.version_after = get_current_version();
|
||||
|
||||
if (version_before && update_info.version_after && version_before !== update_info.version_after) {
|
||||
update_info.update_successful = true;
|
||||
update_info.has_update = false;
|
||||
update_info.status = 'updated';
|
||||
update_info.message = `Update successful: ${version_before} → ${update_info.version_after}`;
|
||||
}
|
||||
else if (match(output, /Current version.*is the latest/)) {
|
||||
update_info.status = 'latest';
|
||||
update_info.has_update = false;
|
||||
let version_match = match(output, /v[\d.]+/);
|
||||
if (version_match) {
|
||||
update_info.current_version = replace(version_match[0], /^v/, '');
|
||||
update_info.latest_version = update_info.current_version;
|
||||
}
|
||||
update_info.message = 'Already the latest version ' + (update_info.current_version || '');
|
||||
|
||||
} else if (match(output, /new version.*available/)) {
|
||||
update_info.status = 'update_available';
|
||||
update_info.has_update = true;
|
||||
|
||||
let versions = match(output, /v[\d.]+/, 'g');
|
||||
if (versions && length(versions) >= 2) {
|
||||
update_info.current_version = replace(versions[0], /^v/, '');
|
||||
update_info.latest_version = replace(versions[1], /^v/, '');
|
||||
} else if (version_before) {
|
||||
update_info.current_version = version_before;
|
||||
}
|
||||
update_info.message = 'New version available: ' + (update_info.latest_version || '');
|
||||
|
||||
} else if (match(output, /download.*failed/)) {
|
||||
update_info.status = 'download_failed';
|
||||
update_info.has_update = false;
|
||||
update_info.message = 'Download update failed';
|
||||
|
||||
} else if (match(output, /check.*failed/)) {
|
||||
update_info.status = 'check_failed';
|
||||
update_info.has_update = false;
|
||||
update_info.message = 'Update check failed';
|
||||
|
||||
} else {
|
||||
update_info.status = 'unknown';
|
||||
update_info.message = output;
|
||||
}
|
||||
|
||||
return { update: update_info };
|
||||
} catch(e) {
|
||||
return { update: {}, error: 'Parse error: ' + e };
|
||||
}
|
||||
} else {
|
||||
return { update: {}, error: 'failed to execute ddns-go command' };
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { 'luci.ddns-go': methods };
|
||||
Reference in New Issue
Block a user