update 2026-10-29 18:46:39

This commit is contained in:
action committed 2026-10-29 18:46:39 +08:00
1 parent e2f86e09f4
commit 95f76f26d3
5034 files changed
+2043234

No files matched your search

@@ -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('&#160;<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
});