diff --git a/tools/deploy_live_paramiko.py b/tools/deploy_live_paramiko.py index 57c3927..00800f9 100644 --- a/tools/deploy_live_paramiko.py +++ b/tools/deploy_live_paramiko.py @@ -267,16 +267,18 @@ def main() -> int: f"echo '{PASS}' | sudo -S systemctl restart live-console.service", timeout=120, ) - _safe_print("等待服务监听…") - time.sleep(6) + _safe_print("等待控制面监听(最多约 45s)…") + time.sleep(4) run_logged( client, log_path, "curl_health_after_restart", - "curl -sS -m 15 http://127.0.0.1:8001/health 2>&1 || " - "curl -sS -m 15 http://127.0.0.1:8101/health 2>&1 || " - "echo 'curl_health_failed'", - timeout=25, + "for i in $(seq 1 22); do " + "if curl -sf -m 4 http://127.0.0.1:8001/health >/dev/null 2>&1; then echo \"health_ok_8001\"; exit 0; fi; " + "if curl -sf -m 4 http://127.0.0.1:8101/health >/dev/null 2>&1; then echo \"health_ok_8101\"; exit 0; fi; " + "sleep 2; " + "done; echo curl_health_failed; exit 0", + timeout=120, ) run_logged( client, @@ -300,18 +302,18 @@ def main() -> int: timeout=180, ) # Caddy 刚 enable --now 时 bind :80 可能晚于立即 curl(曾出现 http_code=000) - time.sleep(8) + time.sleep(12) run_logged( client, log_path, "curl_port80_after_edge", - "for i in 1 2 3 4 5; do " - "code=$(curl -sS -m 8 -o /dev/null -w '%{http_code}' http://127.0.0.1/ 2>/dev/null || echo 000); " + "for i in 1 2 3 4 5 6 7 8; do " + "code=$(curl -sS -m 10 -o /dev/null -w '%{http_code}' http://127.0.0.1/ 2>/dev/null || echo 000); " "echo \"try$i http_code=$code\"; " "[ \"$code\" = 200 ] && exit 0; " - "sleep 3; " + "sleep 4; " "done; exit 1", - timeout=60, + timeout=120, ) run_logged( @@ -331,6 +333,14 @@ def main() -> int: timeout=20, ) + run_logged( + client, + log_path, + "docker_srs_neko_ps", + "docker ps -a --format '{{.Names}} {{.Status}}' 2>/dev/null | grep -E 'live-srs|live-neko' || echo '(no live-srs / live-neko in docker ps)'", + timeout=25, + ) + return 0 finally: if sftp: diff --git a/web-console/components/live-product/LiveAndroidWorkstation.tsx b/web-console/components/live-product/LiveAndroidWorkstation.tsx index 0b9f4b6..83d0039 100644 --- a/web-console/components/live-product/LiveAndroidWorkstation.tsx +++ b/web-console/components/live-product/LiveAndroidWorkstation.tsx @@ -1,6 +1,15 @@ "use client"; -import { ArrowUpRight, Layers, Loader2, Package, Terminal } from "lucide-react"; +import { + ArrowUpRight, + ChevronDown, + ChevronUp, + Copy, + Layers, + Loader2, + Package, + Terminal, +} from "lucide-react"; import { useCallback, useEffect, useState } from "react"; type DeviceRow = { serial: string; model?: string; state: string }; @@ -31,6 +40,13 @@ type UiNode = { enabled?: boolean; }; +const SHELL_SNIPPETS: { label: string; cmd: string }[] = [ + { label: "getprop", cmd: "getprop ro.build.version.release" }, + { label: "top pkg", cmd: "dumpsys activity activities | grep -E 'mResumedActivity' | tail -n 1" }, + { label: "sdcard", cmd: "ls -la /sdcard | head -n 24" }, + { label: "net", cmd: "dumpsys connectivity | head -n 40" }, +]; + type Props = { t: TFn; notify: (msg: string) => void; @@ -47,6 +63,7 @@ type Props = { apkPath: string; setApkPath: (v: string) => void; onAndroidAction: (action: string, payload?: Record) => Promise; + onCopy: (text: string) => void; }; export function LiveAndroidWorkstation({ @@ -64,6 +81,7 @@ export function LiveAndroidWorkstation({ apkPath, setApkPath, onAndroidAction, + onCopy, }: Props) { const [overview, setOverview] = useState(null); const [overviewErr, setOverviewErr] = useState(null); @@ -77,6 +95,8 @@ export function LiveAndroidWorkstation({ const [uiNodes, setUiNodes] = useState([]); const [uiErr, setUiErr] = useState(null); const [openUrl, setOpenUrl] = useState("https://"); + const [adbInputText, setAdbInputText] = useState(""); + const [embedScrcpy, setEmbedScrcpy] = useState(false); const [pending, setPending] = useState(null); const lock = !!(busy || pending); @@ -206,6 +226,15 @@ export function LiveAndroidWorkstation({ await onAndroidAction("open_url", { url: u }); }; + const sendAdbText = async () => { + const raw = adbInputText.trim(); + if (!raw) { + notify(t("填写要输入的文字", "Enter text")); + return; + } + await onAndroidAction("text", { text: raw }); + }; + return (
@@ -241,6 +270,14 @@ export function LiveAndroidWorkstation({ ))}
+ {adbAvailable && devices.length === 0 ? ( +

+ {t( + "未检测到已授权设备:请 USB 连接并开启「USB 调试」,手机上允许本计算机调试。", + "No authorized device: enable USB debugging and authorize this computer.", + )} +

+ ) : null}
{adbAvailable && androidSerial ? ( @@ -330,6 +367,71 @@ export function LiveAndroidWorkstation({ > TikTok + + + + +

{t("滑动手势(中心区域)", "Swipe (center)")}

+
+ {(["up", "down", "left", "right"] as const).map((dir) => ( + + ))} +
+
+

+ {t("输入文字(adb input text)", "Type text (adb)")} +

+

+ {t("仅适合英文与数字;中文需剪贴板或无障碍方案。", "ASCII-focused; CJK needs clipboard.")} +

+
+ setAdbInputText(e.target.value)} + placeholder="hello / 123" + onKeyDown={(e) => { + if (e.key === "Enter") void sendAdbText(); + }} + /> + +
setPkgQuery(e.target.value)} placeholder="tiktok / musically / …" + onKeyDown={(e) => { + if (e.key === "Enter") void searchPackages(); + }} />