♻️ refactor(diagnosticTab): improve command execution and UI updates

This commit is contained in:
Ivan K
2025-05-19 20:32:08 +03:00
parent d81a90bd28
commit 1411e7d403

View File

@@ -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) => {
safeExec('/usr/bin/podkop', ['get_sing_box_status'], 0, singboxStatusResult => {
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 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')) {
return createStatus('working', 'working on router', 'SUCCESS'); resolve(createStatus('working', 'working on router', 'SUCCESS'));
} else { } else {
return createStatus('not_working', 'not working on router', 'ERROR'); resolve(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,7 +215,7 @@ 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'),
@@ -179,6 +244,7 @@ function checkDNSAvailability() {
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,8 +265,7 @@ 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) {
@@ -213,29 +278,21 @@ async function checkBypass() {
const controller1 = new AbortController(); const controller1 = new AbortController();
const timeoutId1 = setTimeout(() => controller1.abort(), constants.FETCH_TIMEOUT); const timeoutId1 = setTimeout(() => controller1.abort(), constants.FETCH_TIMEOUT);
const response1 = await cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller1.signal }); cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller1.signal })
const data1 = await response1.json(); .then(response1 => response1.json())
.then(data1 => {
clearTimeout(timeoutId1); clearTimeout(timeoutId1);
ip1 = data1.IP; ip1 = data1.IP;
} catch (error) {
return resolve(createStatus('error', 'First endpoint check failed', 'WARNING'));
}
// Fetch IP from second endpoint // Fetch IP from second endpoint
let ip2 = null;
try {
const controller2 = new AbortController(); const controller2 = new AbortController();
const timeoutId2 = setTimeout(() => controller2.abort(), constants.FETCH_TIMEOUT); const timeoutId2 = setTimeout(() => controller2.abort(), constants.FETCH_TIMEOUT);
const response2 = await cachedFetch(`https://${constants.IP_CHECK_DOMAIN}/check`, { signal: controller2.signal }); cachedFetch(`https://${constants.IP_CHECK_DOMAIN}/check`, { signal: controller2.signal })
const data2 = await response2.json(); .then(response2 => response2.json())
.then(data2 => {
clearTimeout(timeoutId2); clearTimeout(timeoutId2);
const ip2 = data2.IP;
ip2 = data2.IP;
} catch (error) {
return resolve(createStatus('not_working', `${configMode} not working`, 'ERROR'));
}
// Compare IPs // Compare IPs
if (ip1 && ip2) { if (ip1 && ip2) {
@@ -247,6 +304,18 @@ async function checkBypass() {
} else { } else {
return resolve(createStatus('error', 'IP comparison failed', 'WARNING')); 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,9 +324,9 @@ 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 =>
@@ -265,11 +334,9 @@ async function getPodkopErrors() {
); );
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,15 +418,16 @@ 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())
.then(data => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
if (data.fakeip === true) { if (data.fakeip === true) {
@@ -371,11 +439,12 @@ const showConfigModal = async (command, title) => {
} }
// Bypass check // Bypass check
const bypassResponse = await cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller.signal }); cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller.signal })
const bypassData = await bypassResponse.json(); .then(bypassResponse => bypassResponse.json())
const bypassResponse2 = await cachedFetch(`https://${constants.IP_CHECK_DOMAIN}/check`, { signal: controller.signal }); .then(bypassData => {
const bypassData2 = await bypassResponse2.json(); cachedFetch(`https://${constants.IP_CHECK_DOMAIN}/check`, { signal: controller.signal })
.then(bypassResponse2 => bypassResponse2.json())
.then(bypassData2 => {
formattedOutput += '━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'; formattedOutput += '━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
if (bypassData.IP && bypassData2.IP && bypassData.IP !== bypassData2.IP) { if (bypassData.IP && bypassData2.IP && bypassData.IP !== bypassData2.IP) {
@@ -390,19 +459,36 @@ const showConfigModal = async (command, title) => {
} }
updateModalContent(formattedOutput); updateModalContent(formattedOutput);
} catch (error) { })
.catch(error => {
formattedOutput += '\n❌ ' + _('Check failed: ') + (error.name === 'AbortError' ? _('timeout') : error.message) + '\n'; formattedOutput += '\n❌ ' + _('Check failed: ') + (error.name === 'AbortError' ? _('timeout') : error.message) + '\n';
updateModalContent(formattedOutput); 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);
} }
});
} 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,9 +721,9 @@ 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
@@ -672,17 +758,16 @@ async function updateDiagnostics() {
buttons[2].parentNode.replaceChild(autostartButton, buttons[2]); buttons[2].parentNode.replaceChild(autostartButton, buttons[2]);
} }
} }
}) } catch (error) {
.catch(() => {
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
@@ -695,98 +780,74 @@ async function updateDiagnostics() {
'✔ 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'])
.then(result => {
if (result.stdout) { if (result.stdout) {
updateTextElement('openwrt-version', document.createTextNode(result.stdout.split('\n')[1].trim())); updateTextElement('openwrt-version',
updateTextElement('device-model', document.createTextNode(result.stdout.split('\n')[4].trim())); document.createTextNode(result.stdout.split('\n')[1].trim())
);
updateTextElement('device-model',
document.createTextNode(result.stdout.split('\n')[4].trim())
);
} else { } else {
updateTextElement('openwrt-version', document.createTextNode(_('Unknown'))); updateTextElement('openwrt-version', document.createTextNode(_('Unknown')));
updateTextElement('device-model', 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.color}` }, [ E('span', { style: `color: ${result.error ? constants.STATUS_COLORS.WARNING : result.color}` }, [
result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ', result.error ? '! ' : result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
result.state === 'working' ? _('works in browser') : _('not works in browser') result.error ? 'check error' : 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()
.then(result => {
updateTextElement('fakeip-router-status', updateTextElement('fakeip-router-status',
E('span', { style: `color: ${result.color}` }, [ E('span', { style: `color: ${result.error ? constants.STATUS_COLORS.WARNING : result.color}` }, [
result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ', result.error ? '! ' : result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
result.state === 'working' ? _('works on router') : _('not works on router') result.error ? 'check error' : 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',
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')
);
} else {
updateTextElement('dns-remote-status', updateTextElement('dns-remote-status',
E('span', { style: `color: ${result.remote.color}` }, [ E('span', { style: `color: ${result.remote.color}` }, [
result.remote.state === 'available' ? '✔ ' : result.remote.state === 'unavailable' ? '✘ ' : '! ', result.remote.state === 'available' ? '✔ ' : result.remote.state === 'unavailable' ? '✘ ' : '! ',
@@ -800,35 +861,20 @@ async function updateDiagnostics() {
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()
.then(result => {
updateTextElement('bypass-status', updateTextElement('bypass-status',
E('span', { style: `color: ${result.color}` }, [ E('span', { style: `color: ${result.error ? constants.STATUS_COLORS.WARNING : result.color}` }, [
result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ', result.error ? '! ' : result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
result.message result.error ? 'check error' : 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;
// Render UI structure immediately
this.innerHTML = '';
createStatusSection().then(section => { createStatusSection().then(section => {
this.appendChild(section); diagnosticsContainer.appendChild(section);
startDiagnosticsUpdates(); startDiagnosticsUpdates();
startErrorPolling(); startErrorPolling();
}); });
} }
});
} }
const tabs = node.querySelectorAll('.cbi-tabmenu'); const tabs = node.querySelectorAll('.cbi-tabmenu');