mirror of
https://github.com/baoweise-bot/aimili-vpngate.git
synced 2026-09-08 09:17:03 +08:00
fix: resolve country classification filter mismatches, fix table cell text wrapping layout bugs, and remove latency test buttons
This commit is contained in:
+32
-66
@@ -1111,7 +1111,11 @@ def auto_switch_node(attempt: int = 0) -> None:
|
|||||||
]
|
]
|
||||||
|
|
||||||
if routing_mode == "fixed_region" and target_country:
|
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
|
# Apply routing_ip_type filter
|
||||||
routing_ip_type = ui_cfg.get("routing_ip_type", "all")
|
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":
|
if routing_mode != "fixed_ip":
|
||||||
available_candidates = [n for n in merged if n.get("probe_status") == "available"]
|
available_candidates = [n for n in merged if n.get("probe_status") == "available"]
|
||||||
if routing_mode == "fixed_region" and target_country:
|
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
|
# Apply routing_ip_type filter for auto-connect
|
||||||
routing_ip_type = ui_cfg.get("routing_ip_type", "all")
|
routing_ip_type = ui_cfg.get("routing_ip_type", "all")
|
||||||
@@ -2258,7 +2266,8 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
min-width: 1000px;
|
min-width: 1330px;
|
||||||
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
th, td {
|
th, td {
|
||||||
@@ -2274,6 +2283,9 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.8px;
|
letter-spacing: 0.8px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
@@ -2660,10 +2672,6 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
<option value="hosting">机房IP</option>
|
<option value="hosting">机房IP</option>
|
||||||
</select>
|
</select>
|
||||||
<input id="search" placeholder="输入国家、位置、IP、ASN、运营主体等过滤节点..." />
|
<input id="search" placeholder="输入国家、位置、IP、ASN、运营主体等过滤节点..." />
|
||||||
<button id="btn_batch_test" class="btn-primary" style="height: 42px; padding: 0 20px; font-weight: 600; background: var(--primary-gradient);">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" style="width:16px; height:16px;" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
||||||
批量测试本页
|
|
||||||
</button>
|
|
||||||
</section>
|
</section>
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
@@ -2673,9 +2681,9 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
<th style="width: 110px;">状态</th>
|
<th style="width: 110px;">状态</th>
|
||||||
<th style="width: 100px;">延迟</th>
|
<th style="width: 100px;">延迟</th>
|
||||||
<th style="width: 220px;">IP 地址 : 端口</th>
|
<th style="width: 220px;">IP 地址 : 端口</th>
|
||||||
<th>物理位置</th>
|
<th style="width: 180px;">物理位置</th>
|
||||||
<th style="width: 100px;">ASN</th>
|
<th style="width: 100px;">ASN</th>
|
||||||
<th>运营主体 / ISP</th>
|
<th style="width: 240px;">运营主体 / ISP</th>
|
||||||
<th style="width: 110px;">网络质量</th>
|
<th style="width: 110px;">网络质量</th>
|
||||||
<th style="width: 110px;">IP 类型</th>
|
<th style="width: 110px;">IP 类型</th>
|
||||||
<th style="width: 160px;">操作</th>
|
<th style="width: 160px;">操作</th>
|
||||||
@@ -3073,7 +3081,7 @@ function getLatencyClass(ms) {
|
|||||||
function updateCountryFilter() {
|
function updateCountryFilter() {
|
||||||
const select = $("country_filter");
|
const select = $("country_filter");
|
||||||
const selectedValue = select.value;
|
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);
|
const currentOptions = Array.from(select.options).map(o => o.value).filter(Boolean);
|
||||||
if (JSON.stringify(countries) === JSON.stringify(currentOptions)) {
|
if (JSON.stringify(countries) === JSON.stringify(currentOptions)) {
|
||||||
@@ -3097,7 +3105,7 @@ function getFilteredNodes() {
|
|||||||
const selectedStatus = $("status_filter").value;
|
const selectedStatus = $("status_filter").value;
|
||||||
return nodes.filter(n => {
|
return nodes.filter(n => {
|
||||||
if (!n) return false;
|
if (!n) return false;
|
||||||
if (selectedCountry && n.country !== selectedCountry) {
|
if (selectedCountry && translateCountry(n.country) !== selectedCountry) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (selectedIpType) {
|
if (selectedIpType) {
|
||||||
@@ -3311,15 +3319,14 @@ function render(){
|
|||||||
return `<tr ${rowClass}>
|
return `<tr ${rowClass}>
|
||||||
<td><span class="badge ${badgeClass}">${badgeText}</span></td>
|
<td><span class="badge ${badgeClass}">${badgeText}</span></td>
|
||||||
<td>${latencyText}</td>
|
<td>${latencyText}</td>
|
||||||
<td class="mono">${esc(n.ip||n.remote_host)}:${n.remote_port||""}</td>
|
<td class="mono" style="white-space: nowrap; max-width: 220px; overflow: hidden; text-overflow: ellipsis;" title="${esc(n.ip||n.remote_host)}:${n.remote_port||""}">${esc(n.ip||n.remote_host)}:${n.remote_port||""}</td>
|
||||||
<td>${esc(displayLocation)}</td>
|
<td style="white-space: nowrap; max-width: 180px; overflow: hidden; text-overflow: ellipsis;" title="${esc(displayLocation)}">${esc(displayLocation)}</td>
|
||||||
<td class="mono" style="font-size:12px; color:var(--text-secondary);">${esc(n.asn||"-")}</td>
|
<td class="mono" style="font-size:12px; color:var(--text-secondary); white-space: nowrap; max-width: 100px; overflow: hidden; text-overflow: ellipsis;" title="${esc(n.asn||"-")}">${esc(n.asn||"-")}</td>
|
||||||
<td>${esc(n.owner||n.as_name||"-")}</td>
|
<td style="white-space: nowrap; max-width: 240px; overflow: hidden; text-overflow: ellipsis;" title="${esc(n.owner||n.as_name||"-")}">${esc(n.owner||n.as_name||"-")}</td>
|
||||||
<td>${esc(translateQuality(n.quality))}</td>
|
<td style="white-space: nowrap; max-width: 110px; overflow: hidden; text-overflow: ellipsis;" title="${esc(translateQuality(n.quality))}">${esc(translateQuality(n.quality))}</td>
|
||||||
<td>${esc(translateIpType(n.ip_type))}</td>
|
<td style="white-space: nowrap; max-width: 110px; overflow: hidden; text-overflow: ellipsis;" title="${esc(translateIpType(n.ip_type))}">${esc(translateIpType(n.ip_type))}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="table-actions">
|
<div class="table-actions">
|
||||||
${testBtn}
|
|
||||||
${connectBtn}
|
${connectBtn}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -3391,6 +3398,7 @@ function startConnectionPolling() {
|
|||||||
nodes = data.nodes || [];
|
nodes = data.nodes || [];
|
||||||
state = data.state || {};
|
state = data.state || {};
|
||||||
stableSortNodes();
|
stableSortNodes();
|
||||||
|
updateCountryFilter();
|
||||||
render();
|
render();
|
||||||
|
|
||||||
if (!state.is_connecting) {
|
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 = `<svg style="animation: spin 1s linear infinite; width: 14px; height: 14px; display: inline-block; margin-right: 6px; vertical-align: middle;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-opacity="0.2" fill="none"></circle><path d="M4 12a8 8 0 018-8" stroke="currentColor" fill="none"></path></svg>测试中...`;
|
|
||||||
|
|
||||||
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 = `<svg xmlns="http://www.w3.org/2000/svg" style="width:16px; height:16px;" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg> 批量测试本页`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -3641,8 +3605,9 @@ function populateRoutingCountries() {
|
|||||||
if (!select) return;
|
if (!select) return;
|
||||||
const countMap = {};
|
const countMap = {};
|
||||||
nodes.forEach(n => {
|
nodes.forEach(n => {
|
||||||
if (n.country) {
|
const c = translateCountry(n.country);
|
||||||
countMap[n.country] = (countMap[n.country] || 0) + 1;
|
if (c) {
|
||||||
|
countMap[c] = (countMap[c] || 0) + 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3657,7 +3622,7 @@ function populateRoutingCountries() {
|
|||||||
const mode = state.routing_mode || "auto";
|
const mode = state.routing_mode || "auto";
|
||||||
const modeSelect = $("net_routing_mode");
|
const modeSelect = $("net_routing_mode");
|
||||||
if (modeSelect) modeSelect.value = mode;
|
if (modeSelect) modeSelect.value = mode;
|
||||||
select.value = state.force_country || "";
|
select.value = translateCountry(state.force_country) || "";
|
||||||
handleRoutingModeChange(mode);
|
handleRoutingModeChange(mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3879,6 +3844,7 @@ setInterval(async () => {
|
|||||||
nodes = d.nodes || [];
|
nodes = d.nodes || [];
|
||||||
state = d.state || {};
|
state = d.state || {};
|
||||||
stableSortNodes();
|
stableSortNodes();
|
||||||
|
updateCountryFilter();
|
||||||
render();
|
render();
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user