feat: add status filtering UI for VPN nodes and enhance OpenVPN log parsing with error classification

This commit is contained in:
baoweise-bot
2026-06-05 19:09:00 +08:00
parent c155e294a4
commit 7fa2109a09
2 changed files with 28 additions and 3 deletions
+3
View File
@@ -556,6 +556,9 @@ def diagnose_openvpn_failure(log_tail: list[str]) -> tuple[int, str]:
if "connection refused" in joined_log:
return 2004, "[ERR_OVPN_NODE_UNREACHABLE] 节点连接被拒绝。原因: 目的服务器未在指定端口监听,或者主动拒绝了连接。"
if "permission denied" in joined_log or "root privileges" in joined_log or "need root" in joined_log:
return 2002, "[ERR_OVPN_PERMISSION_DENIED] 权限不足。原因: 运行 OpenVPN 需要 root 权限,请确保以 root 用户身份或使用 sudo 运行本系统。"
if "options error" in joined_log:
return 2007, "[ERR_OVPN_ROUTE_NOPULL] 获取/解析 PUSH 配置参数冲突。原因: 某些推送选项在当前版本的客户端或配置环境中不可用。"
+25 -3
View File
@@ -739,11 +739,20 @@ def run_openvpn_until_ready(config_file: str, keep_alive: bool, route_nopull: bo
def reader() -> None:
assert process.stdout is not None
for line in process.stdout:
line_str = line.rstrip()
level = "INFO"
line_lower = line_str.lower()
if "error" in line_lower or "failed" in line_lower or "cannot" in line_lower or "fatal" in line_lower or "permission denied" in line_lower:
level = "ERROR"
elif "warning" in line_lower or "warn" in line_lower or "deprecated" in line_lower:
level = "WARNING"
log_to_json(level, "VPN", f"[OpenVPN] {line_str}")
if not startup_done[0]:
lines.put(line.rstrip())
lines.put(line_str)
else:
if keep_alive:
print(f"[OpenVPN] {line.rstrip()}", flush=True)
print(f"[OpenVPN] {line_str}", flush=True)
if not startup_done[0]:
lines.put(None)
@@ -763,7 +772,7 @@ def run_openvpn_until_ready(config_file: str, keep_alive: bool, route_nopull: bo
break
if line:
tail.append(line)
tail = tail[-8:]
tail = tail[-50:]
if keep_alive:
print(f"[OpenVPN] {line}", flush=True)
lower = line.lower()
@@ -2626,6 +2635,11 @@ INDEX_HTML = r"""<!doctype html>
<section class="toolbar">
<select id="status_filter">
<option value="available">可用节点</option>
<option value="all">全部节点</option>
<option value="unavailable">失效节点</option>
</select>
<select id="country_filter">
<option value="">所有国家</option>
</select>
@@ -3069,6 +3083,7 @@ function getFilteredNodes() {
const q = $("search").value.toLowerCase();
const selectedCountry = $("country_filter").value;
const selectedIpType = $("ip_type_filter").value;
const selectedStatus = $("status_filter").value;
return nodes.filter(n => {
if (!n) return false;
if (selectedCountry && n.country !== selectedCountry) {
@@ -3082,6 +3097,12 @@ function getFilteredNodes() {
return false;
}
}
if (selectedStatus === "available" && n.probe_status === "unavailable" && !n.active) {
return false;
}
if (selectedStatus === "unavailable" && (n.probe_status !== "unavailable" || n.active)) {
return false;
}
const searchStr = [
n.country || "", n.country_short || "", n.ip || "", n.remote_host || "", n.proto || "",
translateQuality(n.quality), translateIpType(n.ip_type), n.location || "", n.owner || "", n.as_name || ""
@@ -3498,6 +3519,7 @@ async function load(){
$("search").oninput=()=>{ currentPage = 1; render(); };
$("country_filter").onchange=()=>{ currentPage = 1; render(); };
$("ip_type_filter").onchange=()=>{ currentPage = 1; render(); };
$("status_filter").onchange=()=>{ currentPage = 1; render(); };
$("refresh").onclick=async()=>{
$("refresh").disabled=true;