mirror of
https://github.com/itdoginfo/podkop.git
synced 2025-12-16 08:26:50 +03:00
♻️ refactor(diagnosticTab): improve command execution and UI updates
This commit is contained in:
@@ -36,8 +36,91 @@ async function cachedFetch(url, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper Functions
|
// Helper functions for command execution with prioritization
|
||||||
// Common status object creator
|
function safeExec(command, args, priority, callback, timeout = constants.COMMAND_TIMEOUT) {
|
||||||
|
priority = (typeof priority === 'number') ? priority : 0;
|
||||||
|
|
||||||
|
const executeCommand = async () => {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (callback && typeof callback === 'function') {
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Command execution failed or timed out: ${command} ${args.join(' ')}`);
|
||||||
|
const errorResult = { stdout: '', stderr: error.message, error: error };
|
||||||
|
|
||||||
|
if (callback && typeof callback === 'function') {
|
||||||
|
callback(errorResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorResult;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (callback && typeof callback === 'function') {
|
||||||
|
setTimeout(executeCommand, constants.RUN_PRIORITY[priority]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return executeCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runCheck(checkFunction, priority, callback) {
|
||||||
|
priority = (typeof priority === 'number') ? priority : 0;
|
||||||
|
|
||||||
|
const executeCheck = async () => {
|
||||||
|
try {
|
||||||
|
const result = await checkFunction();
|
||||||
|
if (callback && typeof callback === 'function') {
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
if (callback && typeof callback === 'function') {
|
||||||
|
callback({ error });
|
||||||
|
}
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (callback && typeof callback === 'function') {
|
||||||
|
setTimeout(executeCheck, constants.RUN_PRIORITY[priority]);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return executeCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runAsyncTask(taskFunction, priority) {
|
||||||
|
priority = (typeof priority === 'number') ? priority : 0;
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
await taskFunction();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Async task error:', error);
|
||||||
|
}
|
||||||
|
}, constants.RUN_PRIORITY[priority]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper Functions for UI and formatting
|
||||||
function createStatus(state, message, color) {
|
function createStatus(state, message, color) {
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
@@ -54,7 +137,7 @@ function formatDiagnosticOutput(output) {
|
|||||||
.replace(/\r/g, '\n');
|
.replace(/\r/g, '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyToClipboard = (text, button) => {
|
function copyToClipboard(text, button) {
|
||||||
const textarea = document.createElement('textarea');
|
const textarea = document.createElement('textarea');
|
||||||
textarea.value = text;
|
textarea.value = text;
|
||||||
document.body.appendChild(textarea);
|
document.body.appendChild(textarea);
|
||||||
@@ -68,36 +151,14 @@ const copyToClipboard = (text, button) => {
|
|||||||
ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message));
|
ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message));
|
||||||
}
|
}
|
||||||
document.body.removeChild(textarea);
|
document.body.removeChild(textarea);
|
||||||
};
|
}
|
||||||
|
|
||||||
// IP masking function
|
// IP masking function
|
||||||
const maskIP = (ip) => {
|
function maskIP(ip) {
|
||||||
if (!ip) return '';
|
if (!ip) return '';
|
||||||
const parts = ip.split('.');
|
const parts = ip.split('.');
|
||||||
if (parts.length !== 4) return ip;
|
if (parts.length !== 4) return ip;
|
||||||
return ['XX', 'XX', 'XX', parts[3]].join('.');
|
return ['XX', 'XX', 'XX', parts[3]].join('.');
|
||||||
};
|
|
||||||
|
|
||||||
async function safeExec(command, args = [], timeout = constants.COMMAND_TIMEOUT) {
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status Check Functions
|
// Status Check Functions
|
||||||
@@ -128,20 +189,24 @@ async function checkFakeIP() {
|
|||||||
|
|
||||||
async function checkFakeIPCLI() {
|
async function checkFakeIPCLI() {
|
||||||
try {
|
try {
|
||||||
const singboxStatusResult = await safeExec('/usr/bin/podkop', ['get_sing_box_status']);
|
return new Promise((resolve) => {
|
||||||
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
|
safeExec('/usr/bin/podkop', ['get_sing_box_status'], 0, singboxStatusResult => {
|
||||||
|
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
|
||||||
|
|
||||||
if (!singboxStatus.running) {
|
if (!singboxStatus.running) {
|
||||||
return createStatus('not_working', 'sing-box not running', 'ERROR');
|
resolve(createStatus('not_working', 'sing-box not running', 'ERROR'));
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await safeExec('nslookup', ['-timeout=2', constants.FAKEIP_CHECK_DOMAIN, '127.0.0.42']);
|
safeExec('nslookup', ['-timeout=2', constants.FAKEIP_CHECK_DOMAIN, '127.0.0.42'], 0, result => {
|
||||||
|
if (result.stdout && result.stdout.includes('198.18')) {
|
||||||
if (result.stdout && result.stdout.includes('198.18')) {
|
resolve(createStatus('working', 'working on router', 'SUCCESS'));
|
||||||
return createStatus('working', 'working on router', 'SUCCESS');
|
} else {
|
||||||
} else {
|
resolve(createStatus('not_working', 'not working on router', 'ERROR'));
|
||||||
return createStatus('not_working', 'not working on router', 'ERROR');
|
}
|
||||||
}
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return createStatus('error', 'CLI check error', 'WARNING');
|
return createStatus('error', 'CLI check error', 'WARNING');
|
||||||
}
|
}
|
||||||
@@ -150,35 +215,36 @@ async function checkFakeIPCLI() {
|
|||||||
function checkDNSAvailability() {
|
function checkDNSAvailability() {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
try {
|
try {
|
||||||
const dnsStatusResult = await safeExec('/usr/bin/podkop', ['check_dns_available']);
|
safeExec('/usr/bin/podkop', ['check_dns_available'], 0, dnsStatusResult => {
|
||||||
if (!dnsStatusResult || !dnsStatusResult.stdout) {
|
if (!dnsStatusResult || !dnsStatusResult.stdout) {
|
||||||
return resolve({
|
return resolve({
|
||||||
remote: createStatus('error', 'DNS check timeout', 'WARNING'),
|
remote: createStatus('error', 'DNS check timeout', 'WARNING'),
|
||||||
local: createStatus('error', 'DNS check timeout', 'WARNING')
|
local: createStatus('error', 'DNS check timeout', 'WARNING')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dnsStatus = JSON.parse(dnsStatusResult.stdout);
|
const dnsStatus = JSON.parse(dnsStatusResult.stdout);
|
||||||
|
|
||||||
const remoteStatus = dnsStatus.is_available ?
|
const remoteStatus = dnsStatus.is_available ?
|
||||||
createStatus('available', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) available`, 'SUCCESS') :
|
createStatus('available', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) available`, 'SUCCESS') :
|
||||||
createStatus('unavailable', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) unavailable`, 'ERROR');
|
createStatus('unavailable', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) unavailable`, 'ERROR');
|
||||||
|
|
||||||
const localStatus = dnsStatus.local_dns_working ?
|
const localStatus = dnsStatus.local_dns_working ?
|
||||||
createStatus('available', 'Router DNS working', 'SUCCESS') :
|
createStatus('available', 'Router DNS working', 'SUCCESS') :
|
||||||
createStatus('unavailable', 'Router DNS not working', 'ERROR');
|
createStatus('unavailable', 'Router DNS not working', 'ERROR');
|
||||||
|
|
||||||
return resolve({
|
return resolve({
|
||||||
remote: remoteStatus,
|
remote: remoteStatus,
|
||||||
local: localStatus
|
local: localStatus
|
||||||
});
|
});
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
return resolve({
|
return resolve({
|
||||||
remote: createStatus('error', 'DNS check parse error', 'WARNING'),
|
remote: createStatus('error', 'DNS check parse error', 'WARNING'),
|
||||||
local: createStatus('error', 'DNS check parse error', 'WARNING')
|
local: createStatus('error', 'DNS check parse error', 'WARNING')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return resolve({
|
return resolve({
|
||||||
remote: createStatus('error', 'DNS check error', 'WARNING'),
|
remote: createStatus('error', 'DNS check error', 'WARNING'),
|
||||||
@@ -199,54 +265,57 @@ async function checkBypass() {
|
|||||||
console.error('Error getting mode from UCI:', e);
|
console.error('Error getting mode from UCI:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if sing-box is running
|
safeExec('/usr/bin/podkop', ['get_sing_box_status'], 0, singboxStatusResult => {
|
||||||
const singboxStatusResult = await safeExec('/usr/bin/podkop', ['get_sing_box_status']);
|
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
|
||||||
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
|
|
||||||
|
|
||||||
if (!singboxStatus.running) {
|
if (!singboxStatus.running) {
|
||||||
return resolve(createStatus('not_working', `${configMode} not running`, 'ERROR'));
|
return resolve(createStatus('not_working', `${configMode} not running`, 'ERROR'));
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch IP from first endpoint
|
|
||||||
let ip1 = null;
|
|
||||||
try {
|
|
||||||
const controller1 = new AbortController();
|
|
||||||
const timeoutId1 = setTimeout(() => controller1.abort(), constants.FETCH_TIMEOUT);
|
|
||||||
|
|
||||||
const response1 = await cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller1.signal });
|
|
||||||
const data1 = await response1.json();
|
|
||||||
clearTimeout(timeoutId1);
|
|
||||||
|
|
||||||
ip1 = data1.IP;
|
|
||||||
} catch (error) {
|
|
||||||
return resolve(createStatus('error', 'First endpoint check failed', 'WARNING'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch IP from second endpoint
|
|
||||||
let ip2 = null;
|
|
||||||
try {
|
|
||||||
const controller2 = new AbortController();
|
|
||||||
const timeoutId2 = setTimeout(() => controller2.abort(), constants.FETCH_TIMEOUT);
|
|
||||||
|
|
||||||
const response2 = await cachedFetch(`https://${constants.IP_CHECK_DOMAIN}/check`, { signal: controller2.signal });
|
|
||||||
const data2 = await response2.json();
|
|
||||||
clearTimeout(timeoutId2);
|
|
||||||
|
|
||||||
ip2 = data2.IP;
|
|
||||||
} catch (error) {
|
|
||||||
return resolve(createStatus('not_working', `${configMode} not working`, 'ERROR'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare IPs
|
|
||||||
if (ip1 && ip2) {
|
|
||||||
if (ip1 !== ip2) {
|
|
||||||
return resolve(createStatus('working', `${configMode} working correctly`, 'SUCCESS'));
|
|
||||||
} else {
|
|
||||||
return resolve(createStatus('not_working', `${configMode} routing incorrect`, 'ERROR'));
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return resolve(createStatus('error', 'IP comparison failed', 'WARNING'));
|
// Fetch IP from first endpoint
|
||||||
}
|
let ip1 = null;
|
||||||
|
try {
|
||||||
|
const controller1 = new AbortController();
|
||||||
|
const timeoutId1 = setTimeout(() => controller1.abort(), constants.FETCH_TIMEOUT);
|
||||||
|
|
||||||
|
cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller1.signal })
|
||||||
|
.then(response1 => response1.json())
|
||||||
|
.then(data1 => {
|
||||||
|
clearTimeout(timeoutId1);
|
||||||
|
ip1 = data1.IP;
|
||||||
|
|
||||||
|
// Fetch IP from second endpoint
|
||||||
|
const controller2 = new AbortController();
|
||||||
|
const timeoutId2 = setTimeout(() => controller2.abort(), constants.FETCH_TIMEOUT);
|
||||||
|
|
||||||
|
cachedFetch(`https://${constants.IP_CHECK_DOMAIN}/check`, { signal: controller2.signal })
|
||||||
|
.then(response2 => response2.json())
|
||||||
|
.then(data2 => {
|
||||||
|
clearTimeout(timeoutId2);
|
||||||
|
const ip2 = data2.IP;
|
||||||
|
|
||||||
|
// Compare IPs
|
||||||
|
if (ip1 && ip2) {
|
||||||
|
if (ip1 !== ip2) {
|
||||||
|
return resolve(createStatus('working', `${configMode} working correctly`, 'SUCCESS'));
|
||||||
|
} else {
|
||||||
|
return resolve(createStatus('not_working', `${configMode} routing incorrect`, 'ERROR'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return resolve(createStatus('error', 'IP comparison failed', 'WARNING'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
return resolve(createStatus('not_working', `${configMode} not working`, 'ERROR'));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
return resolve(createStatus('error', 'First endpoint check failed', 'WARNING'));
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return resolve(createStatus('error', 'Bypass check error', 'WARNING'));
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return resolve(createStatus('error', 'Bypass check error', 'WARNING'));
|
return resolve(createStatus('error', 'Bypass check error', 'WARNING'));
|
||||||
}
|
}
|
||||||
@@ -255,21 +324,19 @@ async function checkBypass() {
|
|||||||
|
|
||||||
// Error Handling
|
// Error Handling
|
||||||
async function getPodkopErrors() {
|
async function getPodkopErrors() {
|
||||||
try {
|
return new Promise(resolve => {
|
||||||
const result = await safeExec('/usr/bin/podkop', ['check_logs']);
|
safeExec('/usr/bin/podkop', ['check_logs'], 0, result => {
|
||||||
if (!result || !result.stdout) return [];
|
if (!result || !result.stdout) return resolve([]);
|
||||||
|
|
||||||
const logs = result.stdout.split('\n');
|
const logs = result.stdout.split('\n');
|
||||||
const errors = logs.filter(log =>
|
const errors = logs.filter(log =>
|
||||||
log.includes('[critical]')
|
log.includes('[critical]')
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Found errors:', errors);
|
console.log('Found errors:', errors);
|
||||||
return errors;
|
resolve(errors);
|
||||||
} catch (error) {
|
});
|
||||||
console.error('Error getting podkop logs:', error);
|
});
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showErrorNotification(error, isMultiple = false) {
|
function showErrorNotification(error, isMultiple = false) {
|
||||||
@@ -281,7 +348,7 @@ function showErrorNotification(error, isMultiple = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Modal Functions
|
// Modal Functions
|
||||||
const createModalContent = (title, content) => {
|
function createModalContent(title, content) {
|
||||||
return [
|
return [
|
||||||
E('div', {
|
E('div', {
|
||||||
'class': 'panel-body',
|
'class': 'panel-body',
|
||||||
@@ -305,9 +372,9 @@ const createModalContent = (title, content) => {
|
|||||||
}, _('Close'))
|
}, _('Close'))
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
};
|
}
|
||||||
|
|
||||||
const showConfigModal = async (command, title) => {
|
function showConfigModal(command, title) {
|
||||||
// Create and show modal immediately with loading state
|
// Create and show modal immediately with loading state
|
||||||
const modalContent = E('div', { 'class': 'panel-body' }, [
|
const modalContent = E('div', { 'class': 'panel-body' }, [
|
||||||
E('div', {
|
E('div', {
|
||||||
@@ -351,58 +418,77 @@ const showConfigModal = async (command, title) => {
|
|||||||
let formattedOutput = '';
|
let formattedOutput = '';
|
||||||
|
|
||||||
if (command === 'global_check') {
|
if (command === 'global_check') {
|
||||||
const res = await safeExec('/usr/bin/podkop', [command]);
|
safeExec('/usr/bin/podkop', [command], 0, res => {
|
||||||
formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), constants.FETCH_TIMEOUT);
|
const timeoutId = setTimeout(() => controller.abort(), constants.FETCH_TIMEOUT);
|
||||||
|
|
||||||
const response = await cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller.signal });
|
cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller.signal })
|
||||||
const data = await response.json();
|
.then(response => response.json())
|
||||||
clearTimeout(timeoutId);
|
.then(data => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (data.fakeip === true) {
|
if (data.fakeip === true) {
|
||||||
formattedOutput += '\n✅ ' + _('FakeIP is working in browser!') + '\n';
|
formattedOutput += '\n✅ ' + _('FakeIP is working in browser!') + '\n';
|
||||||
} else {
|
} else {
|
||||||
formattedOutput += '\n❌ ' + _('FakeIP is not working in browser') + '\n';
|
formattedOutput += '\n❌ ' + _('FakeIP is not working in browser') + '\n';
|
||||||
formattedOutput += _('Check DNS server on current device (PC, phone)') + '\n';
|
formattedOutput += _('Check DNS server on current device (PC, phone)') + '\n';
|
||||||
formattedOutput += _('Its must be router!') + '\n';
|
formattedOutput += _('Its must be router!') + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass check
|
||||||
|
cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller.signal })
|
||||||
|
.then(bypassResponse => bypassResponse.json())
|
||||||
|
.then(bypassData => {
|
||||||
|
cachedFetch(`https://${constants.IP_CHECK_DOMAIN}/check`, { signal: controller.signal })
|
||||||
|
.then(bypassResponse2 => bypassResponse2.json())
|
||||||
|
.then(bypassData2 => {
|
||||||
|
formattedOutput += '━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
||||||
|
|
||||||
|
if (bypassData.IP && bypassData2.IP && bypassData.IP !== bypassData2.IP) {
|
||||||
|
formattedOutput += '✅ ' + _('Proxy working correctly') + '\n';
|
||||||
|
formattedOutput += _('Direct IP: ') + maskIP(bypassData.IP) + '\n';
|
||||||
|
formattedOutput += _('Proxy IP: ') + maskIP(bypassData2.IP) + '\n';
|
||||||
|
} else if (bypassData.IP === bypassData2.IP) {
|
||||||
|
formattedOutput += '❌ ' + _('Proxy is not working - same IP for both domains') + '\n';
|
||||||
|
formattedOutput += _('IP: ') + maskIP(bypassData.IP) + '\n';
|
||||||
|
} else {
|
||||||
|
formattedOutput += '❌ ' + _('Proxy check failed') + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateModalContent(formattedOutput);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
formattedOutput += '\n❌ ' + _('Check failed: ') + (error.name === 'AbortError' ? _('timeout') : error.message) + '\n';
|
||||||
|
updateModalContent(formattedOutput);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
formattedOutput += '\n❌ ' + _('Check failed: ') + (error.name === 'AbortError' ? _('timeout') : error.message) + '\n';
|
||||||
|
updateModalContent(formattedOutput);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
formattedOutput += '\n❌ ' + _('Check failed: ') + (error.name === 'AbortError' ? _('timeout') : error.message) + '\n';
|
||||||
|
updateModalContent(formattedOutput);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
formattedOutput += '\n❌ ' + _('Check failed: ') + error.message + '\n';
|
||||||
|
updateModalContent(formattedOutput);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// Bypass check
|
|
||||||
const bypassResponse = await cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller.signal });
|
|
||||||
const bypassData = await bypassResponse.json();
|
|
||||||
const bypassResponse2 = await cachedFetch(`https://${constants.IP_CHECK_DOMAIN}/check`, { signal: controller.signal });
|
|
||||||
const bypassData2 = await bypassResponse2.json();
|
|
||||||
|
|
||||||
formattedOutput += '━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
||||||
|
|
||||||
if (bypassData.IP && bypassData2.IP && bypassData.IP !== bypassData2.IP) {
|
|
||||||
formattedOutput += '✅ ' + _('Proxy working correctly') + '\n';
|
|
||||||
formattedOutput += _('Direct IP: ') + maskIP(bypassData.IP) + '\n';
|
|
||||||
formattedOutput += _('Proxy IP: ') + maskIP(bypassData2.IP) + '\n';
|
|
||||||
} else if (bypassData.IP === bypassData2.IP) {
|
|
||||||
formattedOutput += '❌ ' + _('Proxy is not working - same IP for both domains') + '\n';
|
|
||||||
formattedOutput += _('IP: ') + maskIP(bypassData.IP) + '\n';
|
|
||||||
} else {
|
|
||||||
formattedOutput += '❌ ' + _('Proxy check failed') + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
updateModalContent(formattedOutput);
|
|
||||||
} catch (error) {
|
|
||||||
formattedOutput += '\n❌ ' + _('Check failed: ') + (error.name === 'AbortError' ? _('timeout') : error.message) + '\n';
|
|
||||||
updateModalContent(formattedOutput);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const res = await safeExec('/usr/bin/podkop', [command]);
|
safeExec('/usr/bin/podkop', [command], 0, res => {
|
||||||
formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
|
||||||
updateModalContent(formattedOutput);
|
updateModalContent(formattedOutput);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateModalContent(_('Error: ') + error.message);
|
updateModalContent(_('Error: ') + error.message);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// Button Factory
|
// Button Factory
|
||||||
const ButtonFactory = {
|
const ButtonFactory = {
|
||||||
@@ -635,200 +721,160 @@ function updateTextElement(elementId, content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateDiagnostics() {
|
async function updateDiagnostics() {
|
||||||
setTimeout(() => {
|
// Podkop Status check
|
||||||
safeExec('/usr/bin/podkop', ['get_status'])
|
safeExec('/usr/bin/podkop', ['get_status'], 0, result => {
|
||||||
.then(result => {
|
try {
|
||||||
const parsedPodkopStatus = JSON.parse(result.stdout || '{"enabled":0,"status":"error"}');
|
const parsedPodkopStatus = JSON.parse(result.stdout || '{"enabled":0,"status":"error"}');
|
||||||
|
|
||||||
// Update Podkop status text
|
// Update Podkop status text
|
||||||
updateTextElement('podkop-status-text',
|
updateTextElement('podkop-status-text',
|
||||||
E('span', {
|
E('span', {
|
||||||
'style': `color: ${parsedPodkopStatus.enabled ? constants.STATUS_COLORS.SUCCESS : constants.STATUS_COLORS.ERROR}`
|
'style': `color: ${parsedPodkopStatus.enabled ? constants.STATUS_COLORS.SUCCESS : constants.STATUS_COLORS.ERROR}`
|
||||||
}, [
|
}, [
|
||||||
parsedPodkopStatus.enabled ? '✔ Autostart enabled' : '✘ Autostart disabled'
|
parsedPodkopStatus.enabled ? '✔ Autostart enabled' : '✘ Autostart disabled'
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update autostart button
|
// Update autostart button
|
||||||
const autostartButton = parsedPodkopStatus.enabled ?
|
const autostartButton = parsedPodkopStatus.enabled ?
|
||||||
ButtonFactory.createInitActionButton({
|
ButtonFactory.createInitActionButton({
|
||||||
label: 'Disable Autostart',
|
label: 'Disable Autostart',
|
||||||
type: 'remove',
|
type: 'remove',
|
||||||
action: 'disable',
|
action: 'disable',
|
||||||
reload: true
|
reload: true
|
||||||
}) :
|
}) :
|
||||||
ButtonFactory.createInitActionButton({
|
ButtonFactory.createInitActionButton({
|
||||||
label: 'Enable Autostart',
|
label: 'Enable Autostart',
|
||||||
type: 'apply',
|
type: 'apply',
|
||||||
action: 'enable',
|
action: 'enable',
|
||||||
reload: true
|
reload: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Find the autostart button and replace it
|
// Find the autostart button and replace it
|
||||||
const panel = document.getElementById('podkop-status-panel');
|
const panel = document.getElementById('podkop-status-panel');
|
||||||
if (panel) {
|
if (panel) {
|
||||||
const buttons = panel.querySelectorAll('.cbi-button');
|
const buttons = panel.querySelectorAll('.cbi-button');
|
||||||
if (buttons.length >= 3) {
|
if (buttons.length >= 3) {
|
||||||
buttons[2].parentNode.replaceChild(autostartButton, buttons[2]);
|
buttons[2].parentNode.replaceChild(autostartButton, buttons[2]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.catch(() => {
|
} catch (error) {
|
||||||
updateTextElement('podkop-status-text',
|
updateTextElement('podkop-status-text',
|
||||||
E('span', { 'style': `color: ${constants.STATUS_COLORS.ERROR}` }, '✘ Error')
|
E('span', { 'style': `color: ${constants.STATUS_COLORS.ERROR}` }, '✘ Error')
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
}, constants.RUN_PRIORITY[0]);
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
// Sing-box Status check
|
||||||
safeExec('/usr/bin/podkop', ['get_sing_box_status'])
|
safeExec('/usr/bin/podkop', ['get_sing_box_status'], 0, result => {
|
||||||
.then(result => {
|
try {
|
||||||
const parsedSingboxStatus = JSON.parse(result.stdout || '{"running":0,"enabled":0,"status":"error"}');
|
const parsedSingboxStatus = JSON.parse(result.stdout || '{"running":0,"enabled":0,"status":"error"}');
|
||||||
|
|
||||||
// Update Sing-box status text
|
// Update Sing-box status text
|
||||||
updateTextElement('singbox-status-text',
|
updateTextElement('singbox-status-text',
|
||||||
E('span', {
|
E('span', {
|
||||||
'style': `color: ${parsedSingboxStatus.running && !parsedSingboxStatus.enabled ?
|
'style': `color: ${parsedSingboxStatus.running && !parsedSingboxStatus.enabled ?
|
||||||
constants.STATUS_COLORS.SUCCESS : constants.STATUS_COLORS.ERROR}`
|
constants.STATUS_COLORS.SUCCESS : constants.STATUS_COLORS.ERROR}`
|
||||||
}, [
|
}, [
|
||||||
parsedSingboxStatus.running && !parsedSingboxStatus.enabled ?
|
parsedSingboxStatus.running && !parsedSingboxStatus.enabled ?
|
||||||
'✔ running' : '✘ ' + parsedSingboxStatus.status
|
'✔ running' : '✘ ' + parsedSingboxStatus.status
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
})
|
} catch (error) {
|
||||||
.catch(() => {
|
updateTextElement('singbox-status-text',
|
||||||
updateTextElement('singbox-status-text',
|
E('span', { 'style': `color: ${constants.STATUS_COLORS.ERROR}` }, '✘ Error')
|
||||||
E('span', { 'style': `color: ${constants.STATUS_COLORS.ERROR}` }, '✘ Error')
|
);
|
||||||
);
|
}
|
||||||
});
|
});
|
||||||
}, constants.RUN_PRIORITY[0]);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
// Version Information checks
|
||||||
safeExec('/usr/bin/podkop', ['show_version'])
|
safeExec('/usr/bin/podkop', ['show_version'], 2, result => {
|
||||||
.then(result => {
|
updateTextElement('podkop-version',
|
||||||
updateTextElement('podkop-version', document.createTextNode(result.stdout ? result.stdout.trim() : _('Unknown')));
|
document.createTextNode(result.stdout ? result.stdout.trim() : _('Unknown'))
|
||||||
})
|
);
|
||||||
.catch(() => {
|
});
|
||||||
updateTextElement('podkop-version', document.createTextNode(_('Error')));
|
|
||||||
});
|
|
||||||
}, constants.RUN_PRIORITY[2]);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
safeExec('/usr/bin/podkop', ['show_luci_version'], 2, result => {
|
||||||
safeExec('/usr/bin/podkop', ['show_luci_version'])
|
updateTextElement('luci-version',
|
||||||
.then(result => {
|
document.createTextNode(result.stdout ? result.stdout.trim() : _('Unknown'))
|
||||||
updateTextElement('luci-version', document.createTextNode(result.stdout ? result.stdout.trim() : _('Unknown')));
|
);
|
||||||
})
|
});
|
||||||
.catch(() => {
|
|
||||||
updateTextElement('luci-version', document.createTextNode(_('Error')));
|
|
||||||
});
|
|
||||||
}, constants.RUN_PRIORITY[2]);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
safeExec('/usr/bin/podkop', ['show_sing_box_version'], 2, result => {
|
||||||
safeExec('/usr/bin/podkop', ['show_sing_box_version'])
|
updateTextElement('singbox-version',
|
||||||
.then(result => {
|
document.createTextNode(result.stdout ? result.stdout.trim() : _('Unknown'))
|
||||||
updateTextElement('singbox-version', document.createTextNode(result.stdout ? result.stdout.trim() : _('Unknown')));
|
);
|
||||||
})
|
});
|
||||||
.catch(() => {
|
|
||||||
updateTextElement('singbox-version', document.createTextNode(_('Error')));
|
|
||||||
});
|
|
||||||
}, constants.RUN_PRIORITY[2]);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
safeExec('/usr/bin/podkop', ['show_system_info'], 2, result => {
|
||||||
safeExec('/usr/bin/podkop', ['show_system_info'])
|
if (result.stdout) {
|
||||||
.then(result => {
|
updateTextElement('openwrt-version',
|
||||||
if (result.stdout) {
|
document.createTextNode(result.stdout.split('\n')[1].trim())
|
||||||
updateTextElement('openwrt-version', document.createTextNode(result.stdout.split('\n')[1].trim()));
|
);
|
||||||
updateTextElement('device-model', document.createTextNode(result.stdout.split('\n')[4].trim()));
|
updateTextElement('device-model',
|
||||||
} else {
|
document.createTextNode(result.stdout.split('\n')[4].trim())
|
||||||
updateTextElement('openwrt-version', document.createTextNode(_('Unknown')));
|
);
|
||||||
updateTextElement('device-model', document.createTextNode(_('Unknown')));
|
} else {
|
||||||
}
|
updateTextElement('openwrt-version', document.createTextNode(_('Unknown')));
|
||||||
})
|
updateTextElement('device-model', document.createTextNode(_('Unknown')));
|
||||||
.catch(() => {
|
}
|
||||||
updateTextElement('openwrt-version', document.createTextNode(_('Error')));
|
});
|
||||||
updateTextElement('device-model', document.createTextNode(_('Error')));
|
|
||||||
});
|
|
||||||
}, constants.RUN_PRIORITY[2]);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
// FakeIP and DNS status checks
|
||||||
checkFakeIP()
|
runCheck(checkFakeIP, 3, result => {
|
||||||
.then(result => {
|
updateTextElement('fakeip-browser-status',
|
||||||
updateTextElement('fakeip-browser-status',
|
E('span', { style: `color: ${result.error ? constants.STATUS_COLORS.WARNING : result.color}` }, [
|
||||||
E('span', { style: `color: ${result.color}` }, [
|
result.error ? '! ' : result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
|
||||||
result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
|
result.error ? 'check error' : result.state === 'working' ? _('works in browser') : _('not works in browser')
|
||||||
result.state === 'working' ? _('works in browser') : _('not works in browser')
|
])
|
||||||
])
|
);
|
||||||
);
|
});
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
updateTextElement('fakeip-browser-status',
|
|
||||||
E('span', { style: `color: ${constants.STATUS_COLORS.WARNING}` }, '! check error')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, constants.RUN_PRIORITY[3]);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
runCheck(checkFakeIPCLI, 3, result => {
|
||||||
checkFakeIPCLI()
|
updateTextElement('fakeip-router-status',
|
||||||
.then(result => {
|
E('span', { style: `color: ${result.error ? constants.STATUS_COLORS.WARNING : result.color}` }, [
|
||||||
updateTextElement('fakeip-router-status',
|
result.error ? '! ' : result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
|
||||||
E('span', { style: `color: ${result.color}` }, [
|
result.error ? 'check error' : result.state === 'working' ? _('works on router') : _('not works on router')
|
||||||
result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
|
])
|
||||||
result.state === 'working' ? _('works on router') : _('not works on router')
|
);
|
||||||
])
|
});
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
updateTextElement('fakeip-router-status',
|
|
||||||
E('span', { style: `color: ${constants.STATUS_COLORS.WARNING}` }, '! check error')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, constants.RUN_PRIORITY[3]);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
runCheck(checkDNSAvailability, 4, result => {
|
||||||
checkDNSAvailability()
|
if (result.error) {
|
||||||
.then(result => {
|
updateTextElement('dns-remote-status',
|
||||||
updateTextElement('dns-remote-status',
|
E('span', { style: `color: ${constants.STATUS_COLORS.WARNING}` }, '! DNS check error')
|
||||||
E('span', { style: `color: ${result.remote.color}` }, [
|
);
|
||||||
result.remote.state === 'available' ? '✔ ' : result.remote.state === 'unavailable' ? '✘ ' : '! ',
|
updateTextElement('dns-local-status',
|
||||||
result.remote.message
|
E('span', { style: `color: ${constants.STATUS_COLORS.WARNING}` }, '! DNS check error')
|
||||||
])
|
);
|
||||||
);
|
} else {
|
||||||
|
updateTextElement('dns-remote-status',
|
||||||
|
E('span', { style: `color: ${result.remote.color}` }, [
|
||||||
|
result.remote.state === 'available' ? '✔ ' : result.remote.state === 'unavailable' ? '✘ ' : '! ',
|
||||||
|
result.remote.message
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
updateTextElement('dns-local-status',
|
updateTextElement('dns-local-status',
|
||||||
E('span', { style: `color: ${result.local.color}` }, [
|
E('span', { style: `color: ${result.local.color}` }, [
|
||||||
result.local.state === 'available' ? '✔ ' : result.local.state === 'unavailable' ? '✘ ' : '! ',
|
result.local.state === 'available' ? '✔ ' : result.local.state === 'unavailable' ? '✘ ' : '! ',
|
||||||
result.local.message
|
result.local.message
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
})
|
}
|
||||||
.catch(() => {
|
});
|
||||||
updateTextElement('dns-remote-status',
|
|
||||||
E('span', { style: `color: ${constants.STATUS_COLORS.WARNING}` }, '! DNS check error')
|
|
||||||
);
|
|
||||||
updateTextElement('dns-local-status',
|
|
||||||
E('span', { style: `color: ${constants.STATUS_COLORS.WARNING}` }, '! DNS check error')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, constants.RUN_PRIORITY[4]);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
runCheck(checkBypass, 1, result => {
|
||||||
checkBypass()
|
updateTextElement('bypass-status',
|
||||||
.then(result => {
|
E('span', { style: `color: ${result.error ? constants.STATUS_COLORS.WARNING : result.color}` }, [
|
||||||
updateTextElement('bypass-status',
|
result.error ? '! ' : result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
|
||||||
E('span', { style: `color: ${result.color}` }, [
|
result.error ? 'check error' : result.message
|
||||||
result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
|
])
|
||||||
result.message
|
);
|
||||||
])
|
});
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
updateTextElement('bypass-status',
|
|
||||||
E('span', { style: `color: ${constants.STATUS_COLORS.WARNING}` }, '! check error')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, constants.RUN_PRIORITY[1]);
|
|
||||||
|
|
||||||
setTimeout(async () => {
|
// Config name
|
||||||
|
runAsyncTask(async () => {
|
||||||
try {
|
try {
|
||||||
let configName = _('Main config');
|
let configName = _('Main config');
|
||||||
const data = await uci.load('podkop');
|
const data = await uci.load('podkop');
|
||||||
@@ -853,7 +899,7 @@ async function updateDiagnostics() {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error getting config name from UCI:', e);
|
console.error('Error getting config name from UCI:', e);
|
||||||
}
|
}
|
||||||
}, constants.RUN_PRIORITY[1]);
|
}, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDiagnosticsSection(mainSection) {
|
function createDiagnosticsSection(mainSection) {
|
||||||
@@ -863,8 +909,8 @@ function createDiagnosticsSection(mainSection) {
|
|||||||
o.rawhtml = true;
|
o.rawhtml = true;
|
||||||
o.cfgvalue = () => E('div', {
|
o.cfgvalue = () => E('div', {
|
||||||
id: 'diagnostics-status',
|
id: 'diagnostics-status',
|
||||||
'style': 'cursor: pointer;'
|
'data-loading': 'true'
|
||||||
}, _('Click to load diagnostics...'));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupDiagnosticsEventHandlers(node) {
|
function setupDiagnosticsEventHandlers(node) {
|
||||||
@@ -885,19 +931,15 @@ function setupDiagnosticsEventHandlers(node) {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const diagnosticsContainer = document.getElementById('diagnostics-status');
|
const diagnosticsContainer = document.getElementById('diagnostics-status');
|
||||||
if (diagnosticsContainer) {
|
if (diagnosticsContainer) {
|
||||||
diagnosticsContainer.addEventListener('click', function () {
|
if (diagnosticsContainer.hasAttribute('data-loading')) {
|
||||||
if (!this.hasAttribute('data-loading')) {
|
diagnosticsContainer.innerHTML = '';
|
||||||
this.setAttribute('data-loading', 'true');
|
showConfigModal.busy = false;
|
||||||
|
createStatusSection().then(section => {
|
||||||
// Render UI structure immediately
|
diagnosticsContainer.appendChild(section);
|
||||||
this.innerHTML = '';
|
startDiagnosticsUpdates();
|
||||||
createStatusSection().then(section => {
|
startErrorPolling();
|
||||||
this.appendChild(section);
|
});
|
||||||
startDiagnosticsUpdates();
|
}
|
||||||
startErrorPolling();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = node.querySelectorAll('.cbi-tabmenu');
|
const tabs = node.querySelectorAll('.cbi-tabmenu');
|
||||||
|
|||||||
Reference in New Issue
Block a user