网络页焕新:IP 画像/质量检测、链路诊断与功能开关

新增 /ip_profile、/ip_quality API 与网络 UI 组件,默认隐藏摄像头/拉流导航;同步更新 README。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 12:47:53 +08:00
parent 119e52cfe9
commit ae9531f2c2
23 changed files with 6966 additions and 543 deletions

View File

@@ -1,45 +1,28 @@
"""
系统与网卡指标(供仪表盘轮询)。下行/上行为全机网卡合计速率(估算)。
本机硬件与系统信息(供「系统」页轮询)。
"""
from __future__ import annotations
import platform
import time
from typing import Any, Optional
from typing import Any
import psutil
_last_net_time: float = 0.0
_last_net: Optional[dict[str, tuple[int, int]]] = None # name -> (sent, recv)
from web2_host_caps import gpu_status_payload, machine_summary_payload
def _net_rates_mbps() -> tuple[Optional[float], Optional[float], dict[str, Any]]:
"""返回 (上行 Mbps, 下行 Mbps, 原始片段)。"""
global _last_net_time, _last_net
now = time.monotonic()
per = psutil.net_io_counters(pernic=True)
cur: dict[str, tuple[int, int]] = {k: (v.bytes_sent, v.bytes_recv) for k, v in per.items()}
detail: dict[str, Any] = {}
up_mbps = down_mbps = None
if _last_net is not None and _last_net_time > 0:
dt = now - _last_net_time
if dt > 0.05:
ds = dr = 0
for name, (sent, recv) in cur.items():
if name in _last_net:
ps0, pr0 = _last_net[name]
ds += max(0, sent - ps0)
dr += max(0, recv - pr0)
up_mbps = round((ds * 8) / dt / 1_000_000, 3)
down_mbps = round((dr * 8) / dt / 1_000_000, 3)
detail["delta_seconds"] = round(dt, 3)
_last_net = cur
_last_net_time = now
for name, (sent, recv) in cur.items():
detail.setdefault("interfaces", {})[name] = {
"bytes_sent": sent,
"bytes_recv": recv,
def _cpu_freq() -> dict[str, float | None] | None:
try:
f = psutil.cpu_freq()
if not f:
return None
return {
"current_mhz": round(f.current, 1) if f.current else None,
"max_mhz": round(f.max, 1) if f.max else None,
}
return up_mbps, down_mbps, detail
except Exception:
return None
def snapshot() -> dict[str, Any]:
@@ -48,6 +31,7 @@ def snapshot() -> dict[str, Any]:
cpu = psutil.cpu_percent(interval=0.15)
except Exception:
cpu = None
try:
vm = psutil.virtual_memory()
mem = {
@@ -58,13 +42,14 @@ def snapshot() -> dict[str, Any]:
}
except Exception:
mem = {}
try:
sm = psutil.swap_memory()
swap = {"percent": sm.percent, "used": sm.used, "total": sm.total}
except Exception:
swap = {}
disks = []
disks: list[dict[str, Any]] = []
try:
for part in psutil.disk_partitions(all=False):
try:
@@ -84,46 +69,32 @@ def snapshot() -> dict[str, Any]:
except Exception:
pass
up_mbps, down_mbps, net_detail = _net_rates_mbps()
boot = None
boot: int | None = None
try:
boot = int(psutil.boot_time())
except Exception:
pass
ffmpeg_procs = []
try:
for p in psutil.process_iter(["pid", "name", "memory_info"]):
try:
name = (p.info.get("name") or "").lower()
if "ffmpeg" not in name:
continue
mi = p.info.get("memory_info")
ffmpeg_procs.append(
{
"pid": p.info["pid"],
"name": p.info.get("name"),
"rss": mi.rss if mi else 0,
}
)
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
except Exception:
pass
machine = machine_summary_payload()
processor = (platform.processor() or "").strip() or None
return {
"ts": time.time(),
"system": {
**machine,
"os_version": platform.version(),
"processor": processor,
"boot_time_unix": boot,
},
"hardware": {
"cpu_logical": psutil.cpu_count(logical=True),
"cpu_physical": psutil.cpu_count(logical=False),
"cpu_freq": _cpu_freq(),
"memory_total": mem.get("total"),
"gpu": gpu_status_payload(),
},
"cpu_percent": cpu,
"cpu_count_logical": psutil.cpu_count(logical=True),
"memory": mem,
"swap": swap,
"disks": disks,
"network": {
"up_mbps": up_mbps,
"down_mbps": down_mbps,
"detail": net_detail,
},
"boot_time_unix": boot,
"ffmpeg_processes": ffmpeg_procs[:24],
}