diff --git a/core/apk_manager.py b/core/apk_manager.py index 6dafdaf..42bfc93 100644 --- a/core/apk_manager.py +++ b/core/apk_manager.py @@ -274,27 +274,36 @@ class ApkManager: def _install_one(self, serial, apk_path, package_name=""): """直连设备安装 APK。 - serial 即设备的 adb 网络地址(IP:5555),直接 adb connect 即可。 + serial 为 adb 网络地址(IP:5555)时直接 adb connect; + 本机 USB 有线设备(serial 无冒号,如 ZY322XXXX)已在 adb 中,无需 connect。 不经过 STF occupy/release,安装完断开连接,app 永久保留。 """ from .adb_helper import _adb self._set_item(serial, "installing", f"正在连接 {serial}...") try: - # adb connect(带重试,设备网络可能需要几次握手) - connected = False - last_out = "" - for attempt in range(1, 4): # 最多 3 次 - out = _adb("connect", serial) - last_out = out - if "connected" in out.lower() and "failed" not in out.lower(): - connected = True - break - _log.info(f"[{serial}] adb connect 第{attempt}次: {out}") - time.sleep(1 + attempt) - if not connected: - _log.warning(f"[{serial}] adb connect 失败: 输出={last_out}") - self._set_item(serial, "failed", f"adb connect 失败: {last_out[:150]}") - return + if ":" in serial: + # 网络设备:adb connect(带重试,设备网络可能需要几次握手) + connected = False + last_out = "" + for attempt in range(1, 4): # 最多 3 次 + out = _adb("connect", serial) + last_out = out + if "connected" in out.lower() and "failed" not in out.lower(): + connected = True + break + _log.info(f"[{serial}] adb connect 第{attempt}次: {out}") + time.sleep(1 + attempt) + if not connected: + _log.warning(f"[{serial}] adb connect 失败: 输出={last_out}") + self._set_item(serial, "failed", f"adb connect 失败: {last_out[:150]}") + return + else: + # 本机 USB 设备:确认已在 adb devices 列表中 + dev_out = _adb("devices") + if serial not in (dev_out or ""): + self._set_item(serial, "failed", + "设备未在本地 adb 列表中,请检查 USB 连接/驱动") + return # 安装 self._set_item(serial, "installing", "正在安装...") diff --git a/core/task_manager.py b/core/task_manager.py index f801231..894cd39 100644 --- a/core/task_manager.py +++ b/core/task_manager.py @@ -458,6 +458,12 @@ class TaskManager: if name in self.groups: del self.groups[name] self._save_groups() + # 同上:显式删数据库行,避免删除的分组重启后复活 + with self._db(): + row = GroupRow.query.filter_by(name=name).first() + if row: + db.session.delete(row) + db.session.commit() return True return False @@ -492,6 +498,13 @@ class TaskManager: if job_id in self.jobs: del self.jobs[job_id] self._save_jobs() + # _save_jobs 只做 upsert 不会删行:必须显式删数据库行, + # 否则删除的任务重启后会从数据库重新加载回来 + with self._db(): + row = JobRow.query.get(job_id) + if row: + db.session.delete(row) + db.session.commit() return True return False diff --git a/static/admin/monitor.js b/static/admin/monitor.js index 9a7570c..b85a8c4 100644 --- a/static/admin/monitor.js +++ b/static/admin/monitor.js @@ -2365,11 +2365,10 @@ async function deleteApk(id){ async function openInstallModal(apkId){ const apk=_apksCache.find(a=>a.id===apkId); const apkName=apk?(apk.display_name||'APK'):'APK'; - const sr=await apiGet('/api/status');if(!sr||!sr.ok)return; - const devices=sr.devices||[]; - const online=devices.filter(d=>d.present); + const r=await apiGet('/api/apks/install/devices'); + const online=(r&&r.ok)?(r.devices||[]):[]; if(!online.length){ - showToast('没有在线设备','error');return; + showToast('没有可用设备(STF 池或本机 adb 均无)','error');return; } const body= '
'+ @@ -2383,11 +2382,11 @@ async function openInstallModal(apkId){ '
'+ '
'+ online.map(d=>{ - const running=d.worker_status==='running'||d.worker_status==='connecting'; - const st=''+(running?'运行中':'空闲')+''; + const isUsb=d.source==='usb'; + const st=''+(isUsb?'本地USB':(d.source==='stf'?'STF':'adb'))+''; // serial 唯一,显示 IP + 型号便于区分同型号设备 - const label=(d.serial||'').replace(/:5555$/,'')+' · '+(d.device_name||d.model||'设备'); - return ''; + const label=(d.serial||'').replace(/:5555$/,'')+' · '+(d.model||(isUsb?'USB 有线设备':'设备')); + return ''; }).join('')+ '
'; diff --git a/web_server.py b/web_server.py index 077b03a..6d8e1c6 100644 --- a/web_server.py +++ b/web_server.py @@ -363,7 +363,7 @@ def api_groups_update(name): @app.route("/api/groups/", methods=["DELETE"]) -@login_required +@perm_required(PERM_TASKS) def api_groups_delete(name): if mgr.delete_group(name): return jsonify({"ok": True, "msg": "分组已删除"}) @@ -728,8 +728,36 @@ def api_apks_delete(apk_id): return jsonify({"ok": False, "error": msg}), 400 +@app.route("/api/apks/install/devices") +@perm_required(PERM_APKS) +def api_apks_install_devices(): + """可安装设备列表(应用管理):STF 在线设备池 + 本机 adb 设备(含 USB 有线连接)。 + + source: stf=STF 池 / usb=本机有线 adb / adb=本机网络 adb。 + """ + devices, seen = [], set() + try: + for d in stf.list_all_devices(): + serial = d.get("serial", "") + if serial and d.get("present") and d.get("ready"): + devices.append({"serial": serial, + "model": d.get("model") or d.get("manufacturer") or "", + "source": "stf"}) + seen.add(serial) + except Exception: + pass + out = _adb("devices") + for line in (out or "").splitlines()[1:]: + parts = line.split() + if len(parts) >= 2 and parts[0] and parts[0] not in seen: + devices.append({"serial": parts[0], "model": "", + "source": "usb" if ":" not in parts[0] else "adb"}) + seen.add(parts[0]) + return jsonify({"ok": True, "devices": devices}) + + @app.route("/api/apks/install", methods=["POST"]) -@login_required +@perm_required(PERM_APKS) def api_apks_install(): """批量安装 APK 到指定设备。参数: {apk_id, serials:[]}""" data = request.json or {}