refactor: replace fs.exec with safeExec for command execution with timeout
This commit is contained in:
@@ -5,6 +5,29 @@
|
|||||||
'require network';
|
'require network';
|
||||||
'require fs';
|
'require fs';
|
||||||
|
|
||||||
|
// Add helper function for safe command execution with timeout
|
||||||
|
async function safeExec(command, args = [], timeout = 3000) {
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
|
const result = await Promise.race([
|
||||||
|
fs.exec(command, args),
|
||||||
|
new Promise((_, reject) => {
|
||||||
|
controller.signal.addEventListener('abort', () => {
|
||||||
|
reject(new Error('Command execution timed out'));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Command execution failed or timed out: ${command} ${args.join(' ')}`);
|
||||||
|
return { stdout: '', stderr: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatDiagnosticOutput(output) {
|
function formatDiagnosticOutput(output) {
|
||||||
if (typeof output !== 'string') return '';
|
if (typeof output !== 'string') return '';
|
||||||
return output.trim()
|
return output.trim()
|
||||||
@@ -53,7 +76,7 @@ function createConfigSection(section, map, network) {
|
|||||||
o.rows = 5;
|
o.rows = 5;
|
||||||
o.ucisection = s.section;
|
o.ucisection = s.section;
|
||||||
o.load = function (section_id) {
|
o.load = function (section_id) {
|
||||||
return fs.exec('/etc/init.d/podkop', ['get_proxy_label', section_id]).then(res => {
|
return safeExec('/etc/init.d/podkop', ['get_proxy_label', section_id]).then(res => {
|
||||||
if (res.stdout) {
|
if (res.stdout) {
|
||||||
try {
|
try {
|
||||||
const decodedLabel = decodeURIComponent(res.stdout.trim());
|
const decodedLabel = decodeURIComponent(res.stdout.trim());
|
||||||
@@ -457,7 +480,7 @@ return view.extend({
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
const m = new form.Map('podkop', _('Podkop configuration'), null, ['main', 'extra']);
|
const m = new form.Map('podkop', _('Podkop configuration'), null, ['main', 'extra']);
|
||||||
fs.exec('/etc/init.d/podkop', ['show_version']).then(res => {
|
safeExec('/etc/init.d/podkop', ['show_version']).then(res => {
|
||||||
if (res.stdout) m.title = _('Podkop') + ' v' + res.stdout.trim();
|
if (res.stdout) m.title = _('Podkop') + ' v' + res.stdout.trim();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -577,23 +600,23 @@ return view.extend({
|
|||||||
podkopStatus.running ?
|
podkopStatus.running ?
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn cbi-button-remove',
|
'class': 'btn cbi-button-remove',
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', ['stop']).then(() => location.reload())
|
'click': () => safeExec('/etc/init.d/podkop', ['stop']).then(() => location.reload())
|
||||||
}, _('Stop Podkop')) :
|
}, _('Stop Podkop')) :
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn cbi-button-apply',
|
'class': 'btn cbi-button-apply',
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', ['start']).then(() => location.reload())
|
'click': () => safeExec('/etc/init.d/podkop', ['start']).then(() => location.reload())
|
||||||
}, _('Start Podkop')),
|
}, _('Start Podkop')),
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn cbi-button-apply',
|
'class': 'btn cbi-button-apply',
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', ['restart']).then(() => location.reload())
|
'click': () => safeExec('/etc/init.d/podkop', ['restart']).then(() => location.reload())
|
||||||
}, _('Restart Podkop')),
|
}, _('Restart Podkop')),
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn cbi-button-' + (podkopStatus.enabled ? 'remove' : 'apply'),
|
'class': 'btn cbi-button-' + (podkopStatus.enabled ? 'remove' : 'apply'),
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', [podkopStatus.enabled ? 'disable' : 'enable']).then(() => location.reload())
|
'click': () => safeExec('/etc/init.d/podkop', [podkopStatus.enabled ? 'disable' : 'enable']).then(() => location.reload())
|
||||||
}, podkopStatus.enabled ? _('Disable Podkop') : _('Enable Podkop')),
|
}, podkopStatus.enabled ? _('Disable Podkop') : _('Enable Podkop')),
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn',
|
'class': 'btn',
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', ['show_config']).then(res => {
|
'click': () => safeExec('/etc/init.d/podkop', ['show_config']).then(res => {
|
||||||
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
||||||
ui.showModal(_('Podkop Configuration'), [
|
ui.showModal(_('Podkop Configuration'), [
|
||||||
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
||||||
@@ -624,7 +647,7 @@ return view.extend({
|
|||||||
}, _('Show Config')),
|
}, _('Show Config')),
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn',
|
'class': 'btn',
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', ['check_logs']).then(res => {
|
'click': () => safeExec('/etc/init.d/podkop', ['check_logs']).then(res => {
|
||||||
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
||||||
ui.showModal(_('Podkop Logs'), [
|
ui.showModal(_('Podkop Logs'), [
|
||||||
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
||||||
@@ -668,7 +691,7 @@ return view.extend({
|
|||||||
E('div', { 'class': 'btn-group', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [
|
E('div', { 'class': 'btn-group', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn',
|
'class': 'btn',
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', ['show_sing_box_config']).then(res => {
|
'click': () => safeExec('/etc/init.d/podkop', ['show_sing_box_config']).then(res => {
|
||||||
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
||||||
ui.showModal(_('Sing-box Configuration'), [
|
ui.showModal(_('Sing-box Configuration'), [
|
||||||
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
||||||
@@ -699,7 +722,7 @@ return view.extend({
|
|||||||
}, _('Show Config')),
|
}, _('Show Config')),
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn',
|
'class': 'btn',
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', ['check_sing_box_logs']).then(res => {
|
'click': () => safeExec('/etc/init.d/podkop', ['check_sing_box_logs']).then(res => {
|
||||||
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
||||||
ui.showModal(_('Sing-box Logs'), [
|
ui.showModal(_('Sing-box Logs'), [
|
||||||
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
||||||
@@ -730,7 +753,7 @@ return view.extend({
|
|||||||
}, _('View Logs')),
|
}, _('View Logs')),
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn',
|
'class': 'btn',
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', ['check_sing_box_connections']).then(res => {
|
'click': () => safeExec('/etc/init.d/podkop', ['check_sing_box_connections']).then(res => {
|
||||||
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
||||||
ui.showModal(_('Active Connections'), [
|
ui.showModal(_('Active Connections'), [
|
||||||
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
||||||
@@ -769,7 +792,7 @@ return view.extend({
|
|||||||
E('div', { 'class': 'btn-group', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [
|
E('div', { 'class': 'btn-group', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn',
|
'class': 'btn',
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', ['check_nft']).then(res => {
|
'click': () => safeExec('/etc/init.d/podkop', ['check_nft']).then(res => {
|
||||||
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
||||||
ui.showModal(_('NFT Rules'), [
|
ui.showModal(_('NFT Rules'), [
|
||||||
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
||||||
@@ -800,7 +823,7 @@ return view.extend({
|
|||||||
}, _('Check NFT Rules')),
|
}, _('Check NFT Rules')),
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn',
|
'class': 'btn',
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', ['check_dnsmasq']).then(res => {
|
'click': () => safeExec('/etc/init.d/podkop', ['check_dnsmasq']).then(res => {
|
||||||
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
||||||
ui.showModal(_('DNSMasq Configuration'), [
|
ui.showModal(_('DNSMasq Configuration'), [
|
||||||
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
||||||
@@ -831,7 +854,7 @@ return view.extend({
|
|||||||
}, _('Check DNSMasq')),
|
}, _('Check DNSMasq')),
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn',
|
'class': 'btn',
|
||||||
'click': () => fs.exec('/etc/init.d/podkop', ['list_update']).then(res => {
|
'click': () => safeExec('/etc/init.d/podkop', ['list_update']).then(res => {
|
||||||
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
||||||
ui.showModal(_('Lists Update Results'), [
|
ui.showModal(_('Lists Update Results'), [
|
||||||
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [
|
||||||
@@ -934,12 +957,12 @@ return view.extend({
|
|||||||
system,
|
system,
|
||||||
fakeipStatus
|
fakeipStatus
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
fs.exec('/etc/init.d/podkop', ['get_status']),
|
safeExec('/etc/init.d/podkop', ['get_status']),
|
||||||
fs.exec('/etc/init.d/podkop', ['get_sing_box_status']),
|
safeExec('/etc/init.d/podkop', ['get_sing_box_status']),
|
||||||
fs.exec('/etc/init.d/podkop', ['show_version']),
|
safeExec('/etc/init.d/podkop', ['show_version']),
|
||||||
fs.exec('/etc/init.d/podkop', ['show_luci_version']),
|
safeExec('/etc/init.d/podkop', ['show_luci_version']),
|
||||||
fs.exec('/etc/init.d/podkop', ['show_sing_box_version']),
|
safeExec('/etc/init.d/podkop', ['show_sing_box_version']),
|
||||||
fs.exec('/etc/init.d/podkop', ['show_system_info']),
|
safeExec('/etc/init.d/podkop', ['show_system_info']),
|
||||||
checkFakeIP()
|
checkFakeIP()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1814,7 +1814,7 @@ get_status() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if service is running
|
# Check if service is running
|
||||||
if pgrep -f "sing-box" >/dev/null; then
|
if pgrep -f "podkop" >/dev/null; then
|
||||||
running=1
|
running=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user