diff --git a/vpngate_manager.py b/vpngate_manager.py index c9c2aba..1e07af1 100644 --- a/vpngate_manager.py +++ b/vpngate_manager.py @@ -1111,7 +1111,11 @@ def auto_switch_node(attempt: int = 0) -> None: ] if routing_mode == "fixed_region" and target_country: - candidates = [n for n in candidates if n.get("country") == target_country] + candidates = [ + n for n in candidates + if n.get("country") == target_country + or vpn_utils.COUNTRY_TRANSLATIONS.get(n.get("country", ""), n.get("country", "")) == target_country + ] # Apply routing_ip_type filter routing_ip_type = ui_cfg.get("routing_ip_type", "all") @@ -1377,7 +1381,11 @@ def maintain_valid_nodes(force: bool = False) -> str: if routing_mode != "fixed_ip": available_candidates = [n for n in merged if n.get("probe_status") == "available"] if routing_mode == "fixed_region" and target_country: - available_candidates = [n for n in available_candidates if n.get("country") == target_country] + available_candidates = [ + n for n in available_candidates + if n.get("country") == target_country + or vpn_utils.COUNTRY_TRANSLATIONS.get(n.get("country", ""), n.get("country", "")) == target_country + ] # Apply routing_ip_type filter for auto-connect routing_ip_type = ui_cfg.get("routing_ip_type", "all") @@ -2258,7 +2266,8 @@ INDEX_HTML = r""" width: 100%; border-collapse: collapse; text-align: left; - min-width: 1000px; + min-width: 1330px; + table-layout: fixed; } th, td { @@ -2274,6 +2283,9 @@ INDEX_HTML = r""" text-transform: uppercase; letter-spacing: 0.8px; color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } tr { @@ -2660,10 +2672,6 @@ INDEX_HTML = r""" -
@@ -2673,9 +2681,9 @@ INDEX_HTML = r""" 状态 延迟 IP 地址 : 端口 - 物理位置 + 物理位置 ASN - 运营主体 / ISP + 运营主体 / ISP 网络质量 IP 类型 操作 @@ -3073,7 +3081,7 @@ function getLatencyClass(ms) { function updateCountryFilter() { const select = $("country_filter"); const selectedValue = select.value; - const countries = Array.from(new Set(nodes.map(n => n.country).filter(Boolean))).sort(); + const countries = Array.from(new Set(nodes.map(n => translateCountry(n.country)).filter(Boolean))).sort(); const currentOptions = Array.from(select.options).map(o => o.value).filter(Boolean); if (JSON.stringify(countries) === JSON.stringify(currentOptions)) { @@ -3097,7 +3105,7 @@ function getFilteredNodes() { const selectedStatus = $("status_filter").value; return nodes.filter(n => { if (!n) return false; - if (selectedCountry && n.country !== selectedCountry) { + if (selectedCountry && translateCountry(n.country) !== selectedCountry) { return false; } if (selectedIpType) { @@ -3311,15 +3319,14 @@ function render(){ return ` ${badgeText} ${latencyText} - ${esc(n.ip||n.remote_host)}:${n.remote_port||""} - ${esc(displayLocation)} - ${esc(n.asn||"-")} - ${esc(n.owner||n.as_name||"-")} - ${esc(translateQuality(n.quality))} - ${esc(translateIpType(n.ip_type))} + ${esc(n.ip||n.remote_host)}:${n.remote_port||""} + ${esc(displayLocation)} + ${esc(n.asn||"-")} + ${esc(n.owner||n.as_name||"-")} + ${esc(translateQuality(n.quality))} + ${esc(translateIpType(n.ip_type))}
- ${testBtn} ${connectBtn}
@@ -3391,6 +3398,7 @@ function startConnectionPolling() { nodes = data.nodes || []; state = data.state || {}; stableSortNodes(); + updateCountryFilter(); render(); if (!state.is_connecting) { @@ -3464,51 +3472,7 @@ async function disconnectNode(){ } } -// Batch test button implementation -$("btn_batch_test").onclick = async () => { - const pageNodes = currentPageNodes || []; - if (pageNodes.length === 0) { - alert("当前页面没有可供测试的备选节点"); - return; - } - - const btn = $("btn_batch_test"); - btn.disabled = true; - btn.innerHTML = `测试中...`; - - pageNodes.forEach(n => testingNodeIds.add(n.id)); - render(); - - const testPromises = pageNodes.map(async (n) => { - const id = n.id; - try { - const response = await fetch("./api/test_node", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ id }) - }); - const result = await response.json(); - if (result.ok && result.node) { - const idx = nodes.findIndex(item => item.id === id); - if (idx !== -1) { - nodes[idx] = result.node; - } - } - } catch (e) { - } finally { - testingNodeIds.delete(id); - render(); - } - }); - - try { - await Promise.all(testPromises); - } catch (e) { - } finally { - btn.disabled = false; - btn.innerHTML = ` 批量测试本页`; - } -}; + @@ -3641,8 +3605,9 @@ function populateRoutingCountries() { if (!select) return; const countMap = {}; nodes.forEach(n => { - if (n.country) { - countMap[n.country] = (countMap[n.country] || 0) + 1; + const c = translateCountry(n.country); + if (c) { + countMap[c] = (countMap[c] || 0) + 1; } }); @@ -3657,7 +3622,7 @@ function populateRoutingCountries() { const mode = state.routing_mode || "auto"; const modeSelect = $("net_routing_mode"); if (modeSelect) modeSelect.value = mode; - select.value = state.force_country || ""; + select.value = translateCountry(state.force_country) || ""; handleRoutingModeChange(mode); } } @@ -3879,6 +3844,7 @@ setInterval(async () => { nodes = d.nodes || []; state = d.state || {}; stableSortNodes(); + updateCountryFilter(); render(); } catch(e) {} }