mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-12 16:47:51 +08:00
update 2026-10-29 11:59:06
This commit is contained in:
1 parent
bc85557d34
commit
e2f86e09f4
5033 files changed
-2043160
No files matched your search
@@ -1,388 +0,0 @@
|
||||
/* 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();
|
||||
}
|
||||
});
|
||||
@@ -1,96 +0,0 @@
|
||||
/* 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
|
||||
});
|
||||
@@ -1,258 +0,0 @@
|
||||
// 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
|
||||
});
|
||||
Reference in new issue
Block a user