From e98c9610f84512c3ffede9d7b1ca93bac1da514a Mon Sep 17 00:00:00 2001 From: kenzok8 Date: Sun, 26 Oct 2025 04:24:05 +0800 Subject: [PATCH] update 2025-10-26 04:24:05 --- luci-app-ddns-go/Makefile | 4 +- .../resources/view/ddns-go/config.js | 311 ++++++++++++++---- luci-app-ddns-go/po/templates/ddns-go.pot | 50 ++- luci-app-ddns-go/po/zh_Hans/ddns-go.po | 50 ++- .../share/rpcd/acl.d/luci-app-ddns-go.json | 38 ++- .../root/usr/share/rpcd/ucode/luci.ddns-go | 135 ++++++++ 6 files changed, 514 insertions(+), 74 deletions(-) create mode 100644 luci-app-ddns-go/root/usr/share/rpcd/ucode/luci.ddns-go diff --git a/luci-app-ddns-go/Makefile b/luci-app-ddns-go/Makefile index f39538b6b..b5ae5035e 100644 --- a/luci-app-ddns-go/Makefile +++ b/luci-app-ddns-go/Makefile @@ -7,8 +7,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-ddns-go -PKG_VERSION:=1.5.4 -PKG_RELEASE:=20250601 +PKG_VERSION:=1.6.3 +PKG_RELEASE:=20251025 PKG_MAINTAINER:=sirpdboy PKG_CONFIG_DEPENDS:= diff --git a/luci-app-ddns-go/htdocs/luci-static/resources/view/ddns-go/config.js b/luci-app-ddns-go/htdocs/luci-static/resources/view/ddns-go/config.js index 5e5292369..112d710cd 100644 --- a/luci-app-ddns-go/htdocs/luci-static/resources/view/ddns-go/config.js +++ b/luci-app-ddns-go/htdocs/luci-static/resources/view/ddns-go/config.js @@ -6,9 +6,21 @@ 'require uci'; 'require form'; 'require poll'; +'require rpc'; +const getDDNSGoInfo = rpc.declare({ + object: 'luci.ddns-go', + method: 'get_ver', + expect: { 'ver': {} } +}); - async function checkProcess() { +const getUpdateInfo = rpc.declare({ + object: 'luci.ddns-go', + method: 'last_update', + expect: { 'update': {} } +}); + +async function checkProcess() { // 先尝试用 pidof try { const pidofRes = await fs.exec('/bin/pidof', ['ddns-go']); @@ -21,8 +33,6 @@ } catch (err) { // pidof 失败,继续尝试 ps } - - // 回退到 ps try { const psRes = await fs.exec('/bin/ps', ['-C', 'ddns-go', '-o', 'pid=']); const pid = psRes.stdout.trim(); @@ -33,29 +43,70 @@ } catch (err) { return { running: false, pid: null }; } - } -function renderStatus(isRunning, listen_port, noweb) { +} + +function getVersionInfo() { + return L.resolveDefault(getDDNSGoInfo(), {}).then(function(result) { + //console.log('getVersionInfo result:', result); + return result || {}; + }).catch(function(error) { + console.error('Failed to get version:', error); + return {}; + }); +} + +function checkUpdateStatus() { + return L.resolveDefault(getUpdateInfo(), {}).then(function(result) { + //console.log('checkUpdateStatus result:', result); + return result || {}; + }).catch(function(error) { + console.error('Failed to get update info:', error); + return {}; + }); +} + +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( - '%s %s %s', - color, icon, _('DDNS-Go'), statusText + '%s %s %s - %s', + color, icon, _('DDNS-Go'), versionText, statusText ); - if (isRunning && res.pid) { - html += ' (PID: ' + res.pid + ')'; + if (isRunning) { + html += String.format(' %s', + window.location.protocol, window.location.hostname, listen_port, _('Open Web Interface')); } - if (isRunning && noweb !== '1') { - const baseUrl = `${window.location.protocol}//${window.location.hostname}`; - const fullUrl = `${baseUrl}:${listen_port}`; - html += String.format(' %s', fullUrl, _('Open Web Interface')); - } - return html; } +function renderUpdateStatus(updateInfo) { + if (!updateInfo || !updateInfo.status) { + return ' ⚠ ' + _('Update status unknown') + ''; + } + + var status = updateInfo.status; + var message = updateInfo.message || ''; + + switch(status) { + case 'updated': + return String.format('✓ %s', message); + case 'update_available': + return String.format('↻ %s', message); + case 'latest': + return String.format('✓ %s', message); + case 'download_failed': + case 'check_failed': + return String.format('✗ %s', message); + default: + return String.format('? %s', message); + } +} + return view.extend({ load: function() { return Promise.all([ @@ -63,6 +114,118 @@ return view.extend({ ]); }, + handleResetPassword: async function () { + try { + ui.showModal(_('Resetting Password'), [ + E('p', { 'class': 'spinning' }, _('Resetting admin password, please wait...')) + ]); + + const result = await fs.exec('/usr/bin/ddns-go', ['-resetPassword', 'admin12345', '-c', '/etc/ddns-go/ddns-go-config.yaml']); + + ui.hideModal(); + + const output = (result.stdout + result.stderr).trim(); + + let success = false; + let message = ''; + + if (result.code === 0) { + + + message = _('Password reset successfully to admin12345'); + + ui.showModal(_('Password Reset Successful'), [ + E('p', _('Admin password has been reset to: 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 { + alert(_('Reset may have failed:') + '\n' + output); + } + + } catch (error) { + ui.hideModal(); + console.error('Reset password failed:', error); + alert(_('ERROR:') + '\n' + _('Reset password failed:') + '\n' + error.message); + } + }, + + handleRestartService: async function() { + try { + await fs.exec('/etc/init.d/ddns-go', ['stop']); + await new Promise(resolve => setTimeout(resolve, 1000)); + 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 = ' ' + _('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(); + } + + // 3秒后恢复显示版本信息 + setTimeout(() => { + var updateView = document.getElementById('update_status'); + if (updateView) { + getVersionInfo().then(function(versionInfo) { + var version = versionInfo.version || ''; + updateView.innerHTML = String.format('✓ %s v%s', + _('Current Version:'), version); + }); + } + }, 3000); + } + + } catch (error) { + console.error('Update failed:', error); + var updateView = document.getElementById('update_status'); + if (updateView) { + updateView.innerHTML = '✗ ' + _('Update failed') + ''; + + // 5秒后恢复显示版本信息 + setTimeout(() => { + getVersionInfo().then(function(versionInfo) { + var version = versionInfo.version || ''; + updateView.innerHTML = String.format('%s v%s', + _('Current Version:'), version); + }); + }, 5000); + } + } + }, + render: function(data) { var m, s, o; var listen_port = (uci.get('ddns-go', 'config', 'port') || '[::]:9876').split(':').slice(-1)[0]; @@ -74,20 +237,27 @@ return view.extend({ // 状态显示部分 s = m.section(form.TypedSection); s.anonymous = true; + s.render = function() { var statusView = E('p', { id: 'control_status' }, ' ' + _('Checking status...')); - var pollInterval = poll.add(function() { - return checkProcess() - .then(function(res) { - statusView.innerHTML = renderStatus(res.running, listen_port, noweb); - }) - .catch(function(err) { - console.error('Status check failed:', err); - statusView.innerHTML = '⚠ ' + _('Status check error') + ''; - }); - }, 5); // 每5秒检查一次 + + 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 = '⚠ ' + _('Status check error') + ''; + }); + }; + + var pollInterval = poll.add(window.statusPoll, 5); // 每5秒检查一次 return E('div', { class: 'cbi-section', id: 'status_bar' }, [ statusView, @@ -102,44 +272,73 @@ return view.extend({ ]) ]) ]); - } + }; + s = m.section(form.NamedSection, 'config', 'basic'); - 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.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 = s.option(form.Value, 'port', _('Listen port')); - o.default = '[::]:9876'; - o.rmempty = false; + o = s.option(form.Value, 'time', _('Update interval')); + o.default = '300'; - o = s.option(form.Value, 'time', _('Update interval')); - o.default = '300'; + o = s.option(form.Value, 'ctimes', _('Compare with service provider N times intervals')); + o.default = '5'; - o = s.option(form.Value, 'ctimes', _('Compare with service provider N times intervals')); - o.default = '5'; + o = s.option(form.Value, 'skipverify', _('Skip verifying certificates')); + o.default = '0'; - o = s.option(form.Value, 'skipverify', _('Skip verifying certificates')); - o.default = '0'; + 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.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.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 = _('ResetPassword'); + o.inputstyle = 'apply'; + o.onclick = L.bind(this.handleResetPassword, 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('v%s', currentVersion); + } + }); + + o.cfgvalue = function() { + return E('div', { style: 'margin: 5px 0;' }, [ + E('span', { id: 'update_status' }, + currentVersion ? String.format('v%s', currentVersion) : _('Loading...')) + ]); + }; - o = s.option(form.Value, 'delay', _('Delayed Start (seconds)')); - o.default = '60'; - - return m.render(); - } -}); + o = s.option(form.Button, '_update', _('Update kernel'), + _('Check and update DDNS-Go to the latest version')); + o.inputtitle = _('Check Update'); + o.inputstyle = 'apply'; + o.onclick = L.bind(this.handleUpdate, this, data); + + return m.render(); + } +}); \ No newline at end of file diff --git a/luci-app-ddns-go/po/templates/ddns-go.pot b/luci-app-ddns-go/po/templates/ddns-go.pot index afee16a6e..4a88c63f0 100644 --- a/luci-app-ddns-go/po/templates/ddns-go.pot +++ b/luci-app-ddns-go/po/templates/ddns-go.pot @@ -43,7 +43,7 @@ msgstr "" msgid "DDNS-GO Service Not Running" msgstr "" -msgid "Please enable the DDNS-GO service" +msgid "DDNS-GO Web Interface Disabled" msgstr "" msgid "Open Web Interface" @@ -72,3 +72,51 @@ msgstr "" msgid "Delayed Start (seconds)" msgstr "" + +msgid "Update kernel" +msgstr "" + +msgid "Check and update DDNS-Go to the latest version" +msgstr "" + +msgid "Check Update" +msgstr "" + +msgid "Updating, please wait..." +msgstr "" + +msgid "Update failed" +msgstr "" + +msgid "Update status unknown" +msgstr "" + +msgid "Reset account password" +msgstr "" + +msgid "ResetPassword" +msgstr "" + +msgid "SUCCESS:" +msgstr "" + +msgid "Password reset successfully to admin12345" +msgstr "" + +msgid "Password Reset Successful" +msgstr "" + +msgid "User password has been reset to: 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 "" +msgstr "" diff --git a/luci-app-ddns-go/po/zh_Hans/ddns-go.po b/luci-app-ddns-go/po/zh_Hans/ddns-go.po index 2c2183ac4..ab61b5436 100644 --- a/luci-app-ddns-go/po/zh_Hans/ddns-go.po +++ b/luci-app-ddns-go/po/zh_Hans/ddns-go.po @@ -20,7 +20,7 @@ msgid "DDNS-GO Control panel" msgstr "DDNS-GO操作台" msgid "Open Web Interface" -msgstr "打开 Web 界面" +msgstr "打开Web界面" msgid "Log" msgstr "日志" @@ -72,3 +72,51 @@ msgstr "不启动web服务" msgid "Delayed Start (seconds)" msgstr "开机延时启动(秒)" + +msgid "Update kernel" +msgstr "更新内核" + +msgid "Check and update DDNS-Go to the latest version" +msgstr "更新DDNS-Go到最新版本" + +msgid "Check Update" +msgstr "检查更新" + +msgid "Updating, please wait..." +msgstr "更新中,请稍等..." + +msgid "Update failed" +msgstr "更新失败" + +msgid "Update status unknown" +msgstr "更新状态未知" + +msgid "Reset account password" +msgstr "重置登录密码" + +msgid "ResetPassword" +msgstr "重置密码" + +msgid "SUCCESS:" +msgstr "完成执行:" + +msgid "Password reset successfully to admin12345" +msgstr "成功重置密码admin12345" + +msgid "Password Reset Successful" +msgstr "重置密码成功" + +msgid "User password has been reset to: admin12345" +msgstr "用户密码重置为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 "" +msgstr "" diff --git a/luci-app-ddns-go/root/usr/share/rpcd/acl.d/luci-app-ddns-go.json b/luci-app-ddns-go/root/usr/share/rpcd/acl.d/luci-app-ddns-go.json index 86aab9232..875d3b830 100644 --- a/luci-app-ddns-go/root/usr/share/rpcd/acl.d/luci-app-ddns-go.json +++ b/luci-app-ddns-go/root/usr/share/rpcd/acl.d/luci-app-ddns-go.json @@ -1,23 +1,33 @@ { - "luci-app-ddns-go": { + "luci-app-ddns-go": { "description": "Grant UCI access for luci-app-ddns-go", - "read": { - "file": { + "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/ddns-go.log": [ "read" ] - }, - "ubus": { - "service": [ "list" ] - }, - "uci": [ "ddns-go" ,"ddns-go" ] - }, - "write": { - "uci": [ "ddns-go" ,"ddns-go" ] - } - } -} + }, + "ubus": { + "rc": [ "*" ], + "service": ["list"], + "luci.ddns-go": [ "*" ] + } + }, + "write": { + "ubus": { + "luci.ddns-go": [ "*" ] + }, + "file": { + "/etc/ddns-go/ddns-go-config.yaml": ["write"] + }, + "uci": ["ddns-go"] + } + } + +} \ No newline at end of file diff --git a/luci-app-ddns-go/root/usr/share/rpcd/ucode/luci.ddns-go b/luci-app-ddns-go/root/usr/share/rpcd/ucode/luci.ddns-go new file mode 100644 index 000000000..8b9a86c60 --- /dev/null +++ b/luci-app-ddns-go/root/usr/share/rpcd/ucode/luci.ddns-go @@ -0,0 +1,135 @@ +#!/usr/bin/ucode +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2021-2025 sirpdboy https://github.com/sirpdboy/luci-app-ddns-go + */ + +'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 { + // 去除开头的 'v' 字符 + 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 = `更新成功: ${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 = '已是最新版本 ' + (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 = '有新版本可用: ' + (update_info.latest_version || ''); + + } else if (match(output, /download.*failed/)) { + update_info.status = 'download_failed'; + update_info.has_update = false; + update_info.message = '下载更新失败'; + + } else if (match(output, /check.*failed/)) { + update_info.status = 'check_failed'; + update_info.has_update = false; + update_info.message = '检查更新失败'; + + } 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 }; \ No newline at end of file