Merge pull request #67 from itdoginfo/feature/no-more-cache

Feature/add DNS and bypass status checks to diagnostics
This commit is contained in:
itdoginfo
2025-03-30 12:37:58 +03:00
committed by GitHub
4 changed files with 453 additions and 36 deletions

View File

@@ -4,6 +4,7 @@
'require ui'; 'require ui';
'require network'; 'require network';
'require fs'; 'require fs';
'require uci';
const STATUS_COLORS = { const STATUS_COLORS = {
SUCCESS: '#4caf50', SUCCESS: '#4caf50',
@@ -90,20 +91,29 @@ function createConfigSection(section, map, network) {
if (cfgvalue) { if (cfgvalue) {
try { try {
// Extract only the active configuration (first non-comment line)
const activeConfig = cfgvalue.split('\n') const activeConfig = cfgvalue.split('\n')
.map(line => line.trim()) .map(line => line.trim())
.find(line => line && !line.startsWith('//')); .find(line => line && !line.startsWith('//'));
if (activeConfig) { if (activeConfig) {
const label = activeConfig.split('#').pop() || 'unnamed'; if (activeConfig.includes('#')) {
const decodedLabel = decodeURIComponent(label); const label = activeConfig.split('#').pop();
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Current config: ') + decodedLabel); if (label && label.trim()) {
container.appendChild(descDiv); const decodedLabel = decodeURIComponent(label);
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Current config: ') + decodedLabel);
container.appendChild(descDiv);
} else {
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv);
}
} else {
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv);
}
} }
} catch (e) { } catch (e) {
console.error('Error parsing config label:', e); console.error('Error parsing config label:', e);
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Current config: ') + (cfgvalue.split('#').pop() || 'unnamed')); const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv); container.appendChild(descDiv);
} }
} else { } else {
@@ -121,7 +131,6 @@ function createConfigSection(section, map, network) {
} }
try { try {
// Get the first non-comment line as the active configuration
const activeConfig = value.split('\n') const activeConfig = value.split('\n')
.map(line => line.trim()) .map(line => line.trim())
.find(line => line && !line.startsWith('//')); .find(line => line && !line.startsWith('//'));
@@ -663,9 +672,8 @@ const createStatusPanel = (title, status, buttons) => {
}; };
// Update the status section creation // Update the status section creation
let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus) { let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus, dnsStatus, bypassStatus, configName) {
return E('div', { 'class': 'cbi-section' }, [ return E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Service Status')),
E('div', { 'class': 'table', style: 'display: flex; gap: 20px;' }, [ E('div', { 'class': 'table', style: 'display: flex; gap: 20px;' }, [
// Podkop Status Panel // Podkop Status Panel
createStatusPanel('Podkop Status', podkopStatus, [ createStatusPanel('Podkop Status', podkopStatus, [
@@ -696,6 +704,11 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
label: 'View Logs', label: 'View Logs',
command: 'check_logs', command: 'check_logs',
title: 'Podkop Logs' title: 'Podkop Logs'
}),
ButtonFactory.createModalButton({
label: _('Update Lists'),
command: 'list_update',
title: _('Lists Update Results')
}) })
]), ]),
@@ -715,6 +728,16 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
label: 'Check Connections', label: 'Check Connections',
command: 'check_sing_box_connections', command: 'check_sing_box_connections',
title: 'Active Connections' title: 'Active Connections'
}),
ButtonFactory.createModalButton({
label: _('Check NFT Rules'),
command: 'check_nft',
title: _('NFT Rules')
}),
ButtonFactory.createModalButton({
label: _('Check DNSMasq'),
command: 'check_dnsmasq',
title: _('DNSMasq Configuration')
}) })
]), ]),
@@ -736,26 +759,34 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
]) ])
]) ])
]), ]),
ButtonFactory.createModalButton({ E('div', { style: 'margin-bottom: 10px;' }, [
label: _('Check NFT Rules'), E('div', { style: 'margin-bottom: 5px;' }, [
command: 'check_nft', E('strong', {}, _('DNS Status')),
title: _('NFT Rules') E('br'),
}), E('span', { style: `color: ${dnsStatus.remote.color}` }, [
ButtonFactory.createModalButton({ dnsStatus.remote.state === 'available' ? '✔' : dnsStatus.remote.state === 'unavailable' ? '✘' : '!',
label: _('Check DNSMasq'), ' ',
command: 'check_dnsmasq', dnsStatus.remote.message
title: _('DNSMasq Configuration') ]),
}), E('br'),
ButtonFactory.createModalButton({ E('span', { style: `color: ${dnsStatus.local.color}` }, [
label: _('Update Lists'), dnsStatus.local.state === 'available' ? '✔' : dnsStatus.local.state === 'unavailable' ? '✘' : '!',
command: 'list_update', ' ',
title: _('Lists Update Results') dnsStatus.local.message
}), ])
ButtonFactory.createModalButton({ ])
label: _('Check Router FakeIP'), ]),
command: 'check_fakeip', E('div', { style: 'margin-bottom: 10px;' }, [
title: _('FakeIP Router Check') E('div', { style: 'margin-bottom: 5px;' }, [
}) E('strong', {}, configName),
E('br'),
E('span', { style: `color: ${bypassStatus.color}` }, [
bypassStatus.state === 'working' ? '✔' : bypassStatus.state === 'not_working' ? '✘' : '!',
' ',
bypassStatus.message
])
])
])
]), ]),
// Version Information Panel // Version Information Panel
@@ -772,6 +803,39 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
]); ]);
}; };
function checkDNSAvailability() {
const createStatus = (state, message, color) => ({
state,
message: _(message),
color: STATUS_COLORS[color]
});
return new Promise(async (resolve) => {
try {
const dnsStatusResult = await safeExec('/usr/bin/podkop', ['check_dns_available']);
const dnsStatus = JSON.parse(dnsStatusResult.stdout || '{"dns_type":"unknown","dns_server":"unknown","is_available":0,"status":"unknown","local_dns_working":0,"local_dns_status":"unknown"}');
const remoteStatus = dnsStatus.is_available ?
createStatus('available', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) available`, 'SUCCESS') :
createStatus('unavailable', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) unavailable`, 'ERROR');
const localStatus = dnsStatus.local_dns_working ?
createStatus('available', 'Router DNS working', 'SUCCESS') :
createStatus('unavailable', 'Router DNS not working', 'ERROR');
return resolve({
remote: remoteStatus,
local: localStatus
});
} catch (error) {
return resolve({
remote: createStatus('error', 'DNS check error', 'WARNING'),
local: createStatus('error', 'DNS check error', 'WARNING')
});
}
});
}
return view.extend({ return view.extend({
async render() { async render() {
document.head.insertAdjacentHTML('beforeend', ` document.head.insertAdjacentHTML('beforeend', `
@@ -1071,6 +1135,82 @@ return view.extend({
}); });
} }
function checkBypass() {
const createStatus = (state, message, color) => ({
state,
message: _(message),
color: STATUS_COLORS[color]
});
return new Promise(async (resolve) => {
try {
let configMode = 'proxy'; // Default fallback
try {
const formData = document.querySelector('form.map-podkop');
if (formData) {
const modeSelect = formData.querySelector('select[name="cbid.podkop.main.mode"]');
if (modeSelect && modeSelect.value) {
configMode = modeSelect.value;
}
}
} catch (formError) {
console.error('Error getting mode from form:', formError);
}
// Check if sing-box is running
const singboxStatusResult = await safeExec('/usr/bin/podkop', ['get_sing_box_status']);
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
if (!singboxStatus.running) {
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(), 10000);
const response1 = await fetch('https://fakeip.tech-domain.club/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(), 10000);
const response2 = await fetch('https://ip.tech-domain.club/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'));
}
} catch (error) {
return resolve(createStatus('error', 'Bypass check error', 'WARNING'));
}
});
}
async function updateDiagnostics() { async function updateDiagnostics() {
try { try {
const [ const [
@@ -1081,7 +1221,9 @@ return view.extend({
singbox, singbox,
system, system,
fakeipStatus, fakeipStatus,
fakeipCLIStatus fakeipCLIStatus,
dnsStatus,
bypassStatus
] = await Promise.all([ ] = await Promise.all([
safeExec('/usr/bin/podkop', ['get_status']), safeExec('/usr/bin/podkop', ['get_status']),
safeExec('/usr/bin/podkop', ['get_sing_box_status']), safeExec('/usr/bin/podkop', ['get_sing_box_status']),
@@ -1090,7 +1232,9 @@ return view.extend({
safeExec('/usr/bin/podkop', ['show_sing_box_version']), safeExec('/usr/bin/podkop', ['show_sing_box_version']),
safeExec('/usr/bin/podkop', ['show_system_info']), safeExec('/usr/bin/podkop', ['show_system_info']),
checkFakeIP(), checkFakeIP(),
checkFakeIPCLI() checkFakeIPCLI(),
checkDNSAvailability(),
checkBypass()
]); ]);
const parsedPodkopStatus = JSON.parse(podkopStatus.stdout || '{"running":0,"enabled":0,"status":"unknown"}'); const parsedPodkopStatus = JSON.parse(podkopStatus.stdout || '{"running":0,"enabled":0,"status":"unknown"}');
@@ -1099,7 +1243,35 @@ return view.extend({
const container = document.getElementById('diagnostics-status'); const container = document.getElementById('diagnostics-status');
if (!container) return; if (!container) return;
const statusSection = createStatusSection(parsedPodkopStatus, parsedSingboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus); let configName = _('Main config');
try {
const data = await uci.load('podkop');
const proxyString = uci.get('podkop', 'main', 'proxy_string');
if (proxyString) {
const activeConfig = proxyString.split('\n')
.map(line => line.trim())
.find(line => line && !line.startsWith('//'));
if (activeConfig) {
if (activeConfig.includes('#')) {
const label = activeConfig.split('#').pop();
if (label && label.trim()) {
configName = _('Config: ') + decodeURIComponent(label);
} else {
configName = _('Main config');
}
} else {
configName = _('Main config');
}
}
}
} catch (e) {
console.error('Error getting config name from UCI:', e);
}
// Create a modified statusSection function with the configName
const statusSection = createStatusSection(parsedPodkopStatus, parsedSingboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus, dnsStatus, bypassStatus, configName);
container.innerHTML = ''; container.innerHTML = '';
container.appendChild(statusSection); container.appendChild(statusSection);
@@ -1118,6 +1290,22 @@ return view.extend({
fakeipCLIStatus.message fakeipCLIStatus.message
]).outerHTML; ]).outerHTML;
} }
const dnsRemoteElement = document.getElementById('dns-remote-status');
if (dnsRemoteElement) {
dnsRemoteElement.innerHTML = E('span', { 'style': `color: ${dnsStatus.remote.color}` }, [
dnsStatus.remote.state === 'available' ? '✔ ' : dnsStatus.remote.state === 'unavailable' ? '✘ ' : '! ',
dnsStatus.remote.message
]).outerHTML;
}
const dnsLocalElement = document.getElementById('dns-local-status');
if (dnsLocalElement) {
dnsLocalElement.innerHTML = E('span', { 'style': `color: ${dnsStatus.local.color}` }, [
dnsStatus.local.state === 'available' ? '✔ ' : dnsStatus.local.state === 'unavailable' ? '✘ ' : '! ',
dnsStatus.local.message
]).outerHTML;
}
} catch (e) { } catch (e) {
const container = document.getElementById('diagnostics-status'); const container = document.getElementById('diagnostics-status');
if (container) { if (container) {
@@ -1142,6 +1330,15 @@ return view.extend({
const titleDiv = E('h2', { 'class': 'cbi-map-title' }, _('Podkop')); const titleDiv = E('h2', { 'class': 'cbi-map-title' }, _('Podkop'));
node.insertBefore(titleDiv, node.firstChild); node.insertBefore(titleDiv, node.firstChild);
document.addEventListener('visibilitychange', function () {
const diagnosticsContainer = document.getElementById('diagnostics-status');
if (document.hidden) {
stopDiagnosticsUpdates();
} else if (diagnosticsContainer && diagnosticsContainer.hasAttribute('data-loading')) {
startDiagnosticsUpdates();
}
});
setTimeout(() => { setTimeout(() => {
const diagnosticsContainer = document.getElementById('diagnostics-status'); const diagnosticsContainer = document.getElementById('diagnostics-status');
if (diagnosticsContainer) { if (diagnosticsContainer) {

View File

@@ -40,8 +40,8 @@ msgstr "Конфигурация Outbound"
msgid "Proxy Configuration URL" msgid "Proxy Configuration URL"
msgstr "URL конфигурации прокси" msgstr "URL конфигурации прокси"
msgid "Enter connection string starting with vless:// or ss:// for proxy configuration" msgid "Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for saving other configs"
msgstr "Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси" msgstr "Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси. Добавляйте комментарии с // для сохранения других конфигураций"
msgid "Outbound Configuration" msgid "Outbound Configuration"
msgstr "Конфигурация исходящего соединения" msgstr "Конфигурация исходящего соединения"
@@ -749,4 +749,67 @@ msgid "not works on router"
msgstr "не работает на роутере" msgstr "не работает на роутере"
msgid "Diagnostics" msgid "Diagnostics"
msgstr "Диагностика" msgstr "Диагностика"
msgid "DNS Status"
msgstr "Статус DNS"
msgid "Bypass Status"
msgstr "Статус обхода"
msgid "proxy working correctly"
msgstr "прокси работает корректно"
msgid "vpn working correctly"
msgstr "vpn работает корректно"
msgid "proxy not working"
msgstr "прокси не работает"
msgid "vpn not working"
msgstr "vpn не работает"
msgid "proxy not running"
msgstr "прокси не запущен"
msgid "vpn not running"
msgstr "vpn не запущен"
msgid "proxy routing incorrect"
msgstr "маршрутизация прокси некорректна"
msgid "vpn routing incorrect"
msgstr "маршрутизация vpn некорректна"
msgid "First endpoint check failed"
msgstr "Проверка первой конечной точки не удалась"
msgid "IP comparison failed"
msgstr "Сравнение IP-адресов не удалось"
msgid "Bypass check error"
msgstr "Ошибка проверки обхода"
msgid "Main config"
msgstr "Основная конфигурация"
msgid "Config without description"
msgstr "Конфигурация без описания"
msgid "DNS working"
msgstr "DNS работает"
msgid "Router DNS working"
msgstr "DNS роутера работает"
msgid "Router DNS not working"
msgstr "DNS роутера не работает"
msgid "DNS check error"
msgstr "Ошибка проверки DNS"
msgid "available"
msgstr "доступен"
msgid "unavailable"
msgstr "недоступен"

View File

@@ -1103,4 +1103,70 @@ msgid "not works on router"
msgstr "" msgstr ""
msgid "Diagnostics" msgid "Diagnostics"
msgstr ""
msgid "DNS Status"
msgstr ""
msgid "Bypass Status"
msgstr ""
msgid "proxy working correctly"
msgstr ""
msgid "vpn working correctly"
msgstr ""
msgid "proxy not working"
msgstr ""
msgid "vpn not working"
msgstr ""
msgid "proxy not running"
msgstr ""
msgid "vpn not running"
msgstr ""
msgid "proxy routing incorrect"
msgstr ""
msgid "vpn routing incorrect"
msgstr ""
msgid "First endpoint check failed"
msgstr ""
msgid "IP comparison failed"
msgstr ""
msgid "Bypass check error"
msgstr ""
msgid "Main config"
msgstr ""
msgid "Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup configs"
msgstr ""
msgid "Config without description"
msgstr ""
msgid "DNS working"
msgstr ""
msgid "Router DNS working"
msgstr ""
msgid "Router DNS not working"
msgstr ""
msgid "DNS check error"
msgstr ""
msgid "available"
msgstr ""
msgid "unavailable"
msgstr "" msgstr ""

View File

@@ -63,6 +63,7 @@ start() {
sing_box_dns sing_box_dns
sing_box_dns_rule_fakeip sing_box_dns_rule_fakeip
sing_box_rule_dns sing_box_rule_dns
sing_box_create_bypass_ruleset
sing_box_add_secure_dns_probe_domain sing_box_add_secure_dns_probe_domain
sing_box_cache_file sing_box_cache_file
process_socks5 process_socks5
@@ -726,6 +727,42 @@ sing_box_dns() {
}' $SING_BOX_CONFIG > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG }' $SING_BOX_CONFIG > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
} }
sing_box_create_bypass_ruleset() {
log "Creating bypass ruleset for direct access"
jq '
.route.rule_set += [{
"tag": "bypass",
"type": "inline",
"rules": [
{
"domain_suffix": [
"ip.tech-domain.club"
]
}
]
}]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
# Add a rule to route bypass domains to direct-out outbound
jq '
.route.rules += [{
"inbound": ["tproxy-in"],
"rule_set": ["bypass"],
"outbound": "main",
"action": "route"
}]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
# Make sure the bypass ruleset is in the fakeip DNS rule
jq '
.dns.rules = (.dns.rules | map(
if .server == "fakeip-server" then
.rule_set += ["bypass"]
else
.
end
))' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
}
sing_box_dns_rule_fakeip() { sing_box_dns_rule_fakeip() {
local rewrite_ttl local rewrite_ttl
config_get rewrite_ttl "main" "dns_rewrite_ttl" "600" config_get rewrite_ttl "main" "dns_rewrite_ttl" "600"
@@ -1985,6 +2022,57 @@ get_status() {
echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\"}" echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\"}"
} }
check_dns_available() {
local dns_type=$(uci get podkop.main.dns_type 2>/dev/null)
local dns_server=$(uci get podkop.main.dns_server 2>/dev/null)
local is_available=0
local status="unavailable"
local local_dns_working=0
local local_dns_status="unavailable"
if [ "$dns_type" = "doh" ]; then
# Different DoH providers use different endpoints and formats
local result=""
# Try common DoH endpoints and check for valid responses
# First try /dns-query endpoint (Cloudflare, AdGuard DNS, etc.)
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/dns-query?name=itdog.info&type=A")
if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then
is_available=1
status="available"
else
# If that fails, try /resolve endpoint (Google DNS)
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/resolve?name=itdog.info&type=A")
if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then
is_available=1
status="available"
fi
fi
elif [ "$dns_type" = "dot" ]; then
nc $dns_server 853 </dev/null >/dev/null 2>&1 & pid=$!
(sleep 3; kill $pid 2>/dev/null) & killpid=$!
wait $pid >/dev/null 2>&1
if [ $? -eq 0 ]; then
is_available=1
status="available"
fi
kill $killpid 2>/dev/null
elif [ "$dns_type" = "udp" ]; then
if nslookup -timeout=2 itdog.info $dns_server >/dev/null 2>&1; then
is_available=1
status="available"
fi
fi
# Check if local DNS resolver is working
if nslookup -timeout=2 $TEST_DOMAIN 127.0.0.1 >/dev/null 2>&1; then
local_dns_working=1
local_dns_status="available"
fi
echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$dns_server\",\"is_available\":$is_available,\"status\":\"$status\",\"local_dns_working\":$local_dns_working,\"local_dns_status\":\"$local_dns_status\"}"
}
sing_box_add_secure_dns_probe_domain() { sing_box_add_secure_dns_probe_domain() {
local domain="$TEST_DOMAIN" local domain="$TEST_DOMAIN"
local override_port=8443 local override_port=8443
@@ -2079,8 +2167,11 @@ case "$1" in
get_sing_box_status) get_sing_box_status)
get_sing_box_status get_sing_box_status
;; ;;
check_dns_available)
check_dns_available
;;
*) *)
echo "Usage: $0 {start|stop|restart|reload|enable|disable|main|list_update|check_proxy|check_nft|check_github|check_logs|check_sing_box_connections|check_sing_box_logs|check_fakeip|check_dnsmasq|show_config|show_version|show_sing_box_config|show_luci_version|show_sing_box_version|show_system_info|get_status|get_sing_box_status}" echo "Usage: $0 {start|stop|restart|reload|enable|disable|main|list_update|check_proxy|check_nft|check_github|check_logs|check_sing_box_connections|check_sing_box_logs|check_fakeip|check_dnsmasq|show_config|show_version|show_sing_box_config|show_luci_version|show_sing_box_version|show_system_info|get_status|get_sing_box_status|check_dns_available}"
exit 1 exit 1
;; ;;
esac esac