This commit is contained in:
eric
2026-07-10 05:15:29 -05:00
parent a0104f6517
commit 924d05d06e
71 changed files with 6815 additions and 680 deletions

View File

@@ -6,11 +6,20 @@
from __future__ import annotations
import asyncio
import json
import socket
import platform
import subprocess
import time
import shutil
from pathlib import Path
from typing import Any, Optional
import httpx
import psutil
from web2_host_caps import machine_summary_payload
from web2_ip_quality import run_ip_quality
# ---------- 目标 ----------
GOOGLE_HOST = "www.google.com"
@@ -361,3 +370,304 @@ async def run_network_dashboard() -> dict[str, Any]:
"logic_version": NETWORK_LOGIC_VERSION,
},
}
def _cli_available(command: str) -> bool:
return shutil.which(command) is not None
def _run_cli(command: list[str], timeout: float = 10.0) -> dict[str, Any]:
kwargs: dict[str, Any] = {
"capture_output": True,
"text": True,
"timeout": timeout,
}
if hasattr(subprocess, "CREATE_NO_WINDOW"):
kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
try:
r = subprocess.run(command, **kwargs)
return {
"ok": r.returncode == 0,
"code": r.returncode,
"stdout": (r.stdout or "").strip(),
"stderr": (r.stderr or "").strip(),
}
except Exception as e:
return {"ok": False, "code": -1, "stdout": "", "stderr": str(e)}
def _safe_json_parse(text: str) -> Optional[dict[str, Any]]:
if not text:
return None
try:
data = json.loads(text)
if isinstance(data, dict):
return data
except Exception:
return None
return None
def _bytes_per_sec_to_mbps(value: Any) -> Optional[float]:
try:
n = float(value)
except (TypeError, ValueError):
return None
if n <= 0:
return None
# Ookla speedtest: bandwidth 为 bytes/sspeedtest-cli: download 为 bits/s
if n > 50_000_000:
return round(n / 1_000_000, 2)
return round(n * 8 / 1_000_000, 2)
def _summarize_speedtest(raw: Any) -> dict[str, Any]:
if not isinstance(raw, dict):
return {}
download = raw.get("download")
upload = raw.get("upload")
ping = raw.get("ping")
down_mbps = None
up_mbps = None
ping_ms = None
if isinstance(download, dict):
down_mbps = _bytes_per_sec_to_mbps(download.get("bandwidth"))
elif download is not None:
down_mbps = _bytes_per_sec_to_mbps(download)
if isinstance(upload, dict):
up_mbps = _bytes_per_sec_to_mbps(upload.get("bandwidth"))
elif upload is not None:
up_mbps = _bytes_per_sec_to_mbps(upload)
if isinstance(ping, dict):
try:
ping_ms = round(float(ping.get("latency")), 1)
except (TypeError, ValueError):
ping_ms = None
elif ping is not None:
try:
ping_ms = round(float(ping), 1)
except (TypeError, ValueError):
ping_ms = None
return {
"download_mbps": down_mbps,
"upload_mbps": up_mbps,
"ping_ms": ping_ms,
}
def _summarize_mtr(raw: Any) -> dict[str, Any]:
if isinstance(raw, dict):
hubs = raw.get("report", {}).get("hubs") if isinstance(raw.get("report"), dict) else raw.get("hubs")
if not isinstance(hubs, list):
hubs = []
last_avg = None
if hubs:
try:
last_avg = float(hubs[-1].get("Avg", hubs[-1].get("avg", 0)) or 0)
except (TypeError, ValueError):
last_avg = None
return {"hops": len(hubs), "last_hop_avg_ms": last_avg}
if isinstance(raw, str) and raw.strip():
lines = [ln for ln in raw.splitlines() if ln.strip() and not ln.startswith("Start:")]
return {"hops": max(0, len(lines) - 1), "last_hop_avg_ms": None}
return {}
def _summarize_ipinfo(raw: Any) -> dict[str, Any]:
if not isinstance(raw, dict):
return {}
return {
"ip": raw.get("ip"),
"city": raw.get("city"),
"region": raw.get("region"),
"country": raw.get("country"),
"org": raw.get("org") or raw.get("asn"),
}
def _systeminformation_payload() -> dict[str, Any]:
vm = psutil.virtual_memory()
cpu = psutil.cpu_percent(interval=0.2)
machine = machine_summary_payload()
disks: list[dict[str, Any]] = []
try:
for part in psutil.disk_partitions(all=False)[:4]:
try:
usage = psutil.disk_usage(part.mountpoint)
disks.append(
{
"mount": part.mountpoint,
"percent": usage.percent,
"free_gb": round(usage.free / (1024**3), 2),
}
)
except (PermissionError, OSError):
continue
except Exception:
pass
net_io: dict[str, Any] = {}
try:
io = psutil.net_io_counters()
net_io = {
"bytes_sent_mb": round(io.bytes_sent / (1024**2), 1),
"bytes_recv_mb": round(io.bytes_recv / (1024**2), 1),
}
except Exception:
pass
return {
"provider": "sebhildebrandt/systeminformation",
"ok": True,
"host": platform.node(),
"platform": platform.platform(),
"machine": machine,
"cpu_logical": psutil.cpu_count(logical=True),
"cpu_percent": round(float(cpu), 1),
"memory_total_gb": round(vm.total / (1024**3), 2),
"memory_used_gb": round(vm.used / (1024**3), 2),
"memory_percent": round(float(vm.percent), 1),
"disks": disks,
"net_io": net_io,
}
def _speedtest_payload() -> dict[str, Any]:
if _cli_available("speedtest"):
result = _run_cli(
["speedtest", "--accept-license", "--accept-gdpr", "-f", "json"],
timeout=18.0,
)
parsed = _safe_json_parse(result.get("stdout", ""))
summary = _summarize_speedtest(parsed)
return {
"provider": "librespeed/speedtest",
"command": "speedtest",
"ok": result.get("ok", False),
"raw": parsed or result.get("stdout", ""),
"summary": summary,
"error": None if result.get("ok", False) else result.get("stderr", "") or "speedtest failed",
}
if _cli_available("speedtest-cli"):
result = _run_cli(["speedtest-cli", "--json"], timeout=18.0)
parsed = _safe_json_parse(result.get("stdout", ""))
summary = _summarize_speedtest(parsed)
return {
"provider": "librespeed/speedtest",
"command": "speedtest-cli",
"ok": result.get("ok", False),
"raw": parsed or result.get("stdout", ""),
"summary": summary,
"error": None if result.get("ok", False) else result.get("stderr", "") or "speedtest-cli failed",
}
return {
"provider": "librespeed/speedtest",
"command": None,
"ok": False,
"raw": None,
"error": "未检测到 speedtest 或 speedtest-cli",
}
def _mtr_payload() -> dict[str, Any]:
if not _cli_available("mtr"):
return {
"provider": "traviscross/mtr",
"ok": False,
"raw": None,
"error": "未检测到 mtr 命令",
}
result = _run_cli(["mtr", "--report", "--json", "-c", "5", "1.1.1.1"], timeout=18.0)
parsed = _safe_json_parse(result.get("stdout", ""))
summary = _summarize_mtr(parsed or result.get("stdout", ""))
return {
"provider": "traviscross/mtr",
"ok": result.get("ok", False),
"raw": parsed or result.get("stdout", ""),
"summary": summary,
"error": None if result.get("ok", False) else result.get("stderr", "") or "mtr failed",
}
def _ipinfo_payload() -> dict[str, Any]:
if not _cli_available("ipinfo"):
return {
"provider": "ipinfo/cli",
"ok": False,
"raw": None,
"error": "未检测到 ipinfo 命令",
}
result = _run_cli(["ipinfo", "--json"], timeout=10.0)
parsed = _safe_json_parse(result.get("stdout", ""))
summary = _summarize_ipinfo(parsed)
return {
"provider": "ipinfo/cli",
"ok": result.get("ok", False),
"raw": parsed or result.get("stdout", ""),
"summary": summary,
"error": None if result.get("ok", False) else result.get("stderr", "") or "ipinfo failed",
}
def _ipquality_toolkit_payload(quality: dict[str, Any]) -> dict[str, Any]:
script = Path(__file__).resolve().parent / "third_party" / "IPQuality" / "ip.sh"
err = quality.get("error")
info = quality.get("info") if isinstance(quality.get("info"), dict) else {}
summary = quality.get("summary") if isinstance(quality.get("summary"), dict) else {}
media = quality.get("media") if isinstance(quality.get("media"), dict) else {}
head = quality.get("head") if isinstance(quality.get("head"), dict) else {}
return {
"provider": "xykt/IPQuality",
"ok": not err,
"script": str(script),
"script_exists": script.is_file(),
"endpoint": "/ip_quality",
"summary": {
"ip": info.get("ip") or head.get("ip"),
"headline": summary.get("headline"),
"proxy_like": summary.get("proxy_like"),
"country": info.get("country"),
"organization": info.get("organization"),
"youtube": (media.get("Youtube") or {}).get("status_label"),
"tiktok": (media.get("TikTok") or {}).get("status_label"),
},
"error": err,
}
async def run_network_toolkit() -> dict[str, Any]:
timeout = httpx.Timeout(READ_TIMEOUT, connect=CONNECT_TIMEOUT)
async with httpx.AsyncClient(timeout=timeout, verify=True) as client:
egress, quality = await asyncio.gather(_probe_default_egress(client), run_ip_quality())
country_code = str(egress.get("country_code") or "").upper()
speed = _speedtest_payload()
mtr = _mtr_payload()
ipinfo = _ipinfo_payload()
return {
"providers": {
"systeminformation": _systeminformation_payload(),
"librespeed_speedtest": speed,
"mtr": mtr,
"ipinfo_cli": ipinfo,
"ipquality": _ipquality_toolkit_payload(quality),
"is_china_user": {
"provider": "yArna/isChinaUser",
"scope": "server_egress",
"ok": bool(country_code == "CN"),
"country_code": country_code or None,
"reason": "服务端出口 IP 国家码(前端 is-china-user 为浏览器侧信号)",
},
"webrtc": {
"provider": "pion/webrtc",
"ok": True,
"note": "前端执行 WebRTC candidate 泄漏检测",
},
"fingerprintjs": {
"provider": "fingerprintjs/fingerprintjs",
"ok": True,
"note": "前端 @fingerprintjs/fingerprintjs 生成 visitorId",
},
},
"meta": {
"at": time.time(),
"logic_version": 2,
},
}