网络页焕新:IP 画像/质量检测、链路诊断与功能开关
新增 /ip_profile、/ip_quality API 与网络 UI 组件,默认隐藏摄像头/拉流导航;同步更新 README。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
64
web2.py
64
web2.py
@@ -20,6 +20,8 @@ from web2_supervisor import (
|
||||
use_builtin,
|
||||
)
|
||||
from web2_network import run_network_dashboard
|
||||
from web2_ip_profile import run_ip_profile
|
||||
from web2_ip_quality import run_ip_quality
|
||||
from web2_proxy_config import read_proxy_config, write_proxy_config
|
||||
from web2_system_metrics import snapshot as system_metrics_snapshot
|
||||
from web2_youtube_oauth import (
|
||||
@@ -77,6 +79,11 @@ from web2_pull_stream import (
|
||||
save_pull_stream_config,
|
||||
)
|
||||
from web2_pocketbase import REQUIRE_PB, pb_auth_refresh_and_validate
|
||||
from web2_feature_flags import (
|
||||
SHOW_CAMERA_FEATURE,
|
||||
SHOW_PULL_STREAM_FEATURE,
|
||||
is_disabled_stream_worker_pm2,
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -122,13 +129,15 @@ def discover_script_entries() -> tuple:
|
||||
if path.is_file() and path.resolve() != web_path:
|
||||
entries.append({"script": path.name, "label": _label_short("youtube")})
|
||||
|
||||
for path in sorted(base.glob("camera*.py")):
|
||||
if path.is_file() and path.resolve() != web_path:
|
||||
entries.append({"script": path.name, "label": _label_short("camera")})
|
||||
if SHOW_CAMERA_FEATURE:
|
||||
for path in sorted(base.glob("camera*.py")):
|
||||
if path.is_file() and path.resolve() != web_path:
|
||||
entries.append({"script": path.name, "label": _label_short("camera")})
|
||||
|
||||
for path in sorted(base.glob("pull_stream*.py")):
|
||||
if path.is_file() and path.resolve() != web_path:
|
||||
entries.append({"script": path.name, "label": _label_short("pull")})
|
||||
if SHOW_PULL_STREAM_FEATURE:
|
||||
for path in sorted(base.glob("pull_stream*.py")):
|
||||
if path.is_file() and path.resolve() != web_path:
|
||||
entries.append({"script": path.name, "label": _label_short("pull")})
|
||||
|
||||
for path in sorted(base.glob("obs*.sh")):
|
||||
if path.is_file():
|
||||
@@ -193,6 +202,8 @@ def relay_channel_id_from_pm2_name(name: str) -> Optional[str]:
|
||||
|
||||
|
||||
def process_valid(name: str) -> bool:
|
||||
if is_disabled_stream_worker_pm2(name):
|
||||
return False
|
||||
if name in VALID_PROCESSES:
|
||||
return True
|
||||
cid = relay_channel_id_from_pm2_name(name)
|
||||
@@ -880,18 +891,28 @@ def _stream_worker_ready_message(process: str) -> str | None:
|
||||
return _camera_ready_message(process) or _pull_stream_ready_message(process)
|
||||
|
||||
|
||||
def _disabled_feature_response(feature: str) -> JSONResponse:
|
||||
return JSONResponse({"error": f"{feature} 功能已禁用"}, status_code=403)
|
||||
|
||||
|
||||
@app.get("/camera/devices")
|
||||
async def camera_devices():
|
||||
if not SHOW_CAMERA_FEATURE:
|
||||
return _disabled_feature_response("摄像头")
|
||||
return list_capture_devices()
|
||||
|
||||
|
||||
@app.get("/camera/config")
|
||||
async def camera_config_get():
|
||||
if not SHOW_CAMERA_FEATURE:
|
||||
return _disabled_feature_response("摄像头")
|
||||
return {"config": load_camera_config(CONFIG_DIR)}
|
||||
|
||||
|
||||
@app.post("/camera/config")
|
||||
async def camera_config_post(request: Request):
|
||||
if not SHOW_CAMERA_FEATURE:
|
||||
return _disabled_feature_response("摄像头")
|
||||
try:
|
||||
data = await request.json()
|
||||
except Exception:
|
||||
@@ -907,16 +928,22 @@ async def camera_config_post(request: Request):
|
||||
|
||||
@app.get("/pull_stream/config")
|
||||
async def pull_stream_config_get():
|
||||
if not SHOW_PULL_STREAM_FEATURE:
|
||||
return _disabled_feature_response("拉流")
|
||||
return {"config": load_pull_stream_config(CONFIG_DIR)}
|
||||
|
||||
|
||||
@app.get("/pull_stream/protocols")
|
||||
async def pull_stream_protocols():
|
||||
if not SHOW_PULL_STREAM_FEATURE:
|
||||
return _disabled_feature_response("拉流")
|
||||
return {"protocols": protocol_help()}
|
||||
|
||||
|
||||
@app.post("/pull_stream/config")
|
||||
async def pull_stream_config_post(request: Request):
|
||||
if not SHOW_PULL_STREAM_FEATURE:
|
||||
return _disabled_feature_response("拉流")
|
||||
try:
|
||||
data = await request.json()
|
||||
except Exception:
|
||||
@@ -1077,6 +1104,31 @@ async def network_status():
|
||||
return JSONResponse({"error": str(e)}, status_code=500)
|
||||
|
||||
|
||||
@app.get("/ip_profile")
|
||||
async def ip_profile(
|
||||
tz: str = Query("", description="客户端 IANA 时区,如 Asia/Shanghai"),
|
||||
lang: str = Query("", description="客户端语言,如 zh-CN"),
|
||||
webrtc_leak: Optional[str] = Query(None, description="1/true 表示检测到 WebRTC 本地泄露"),
|
||||
):
|
||||
"""静态住宅 IP / 出口画像(参考 whoer、ping0.cc)。"""
|
||||
leak_flag: Optional[bool] = None
|
||||
if webrtc_leak is not None:
|
||||
leak_flag = str(webrtc_leak).strip().lower() in ("1", "true", "yes")
|
||||
try:
|
||||
return await run_ip_profile(client_tz=tz.strip(), client_lang=lang.strip(), webrtc_leak=leak_flag)
|
||||
except Exception as e:
|
||||
return JSONResponse({"error": str(e)}, status_code=500)
|
||||
|
||||
|
||||
@app.get("/ip_quality")
|
||||
async def ip_quality():
|
||||
"""IP 质量检测(集成 xykt/IPQuality 数据源与流媒体解锁)。"""
|
||||
try:
|
||||
return await run_ip_quality()
|
||||
except Exception as e:
|
||||
return JSONResponse({"error": str(e)}, status_code=500)
|
||||
|
||||
|
||||
@app.get("/proxy_config")
|
||||
async def get_proxy_config():
|
||||
"""仅作用于录制子进程的代理(不写 Windows 系统代理)。"""
|
||||
|
||||
Reference in New Issue
Block a user