From 3320035c9e9fb95bd72ac6304e896363ec8a6caa Mon Sep 17 00:00:00 2001 From: butubb <1422726308@qq.com> Date: Tue, 11 Aug 2026 08:27:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BB=B4=E6=8A=A4=20Tab=EF=BC=88STF=20?= =?UTF-8?q?=E5=AE=B9=E5=99=A8=E4=B8=80=E9=94=AE=E9=87=8D=E5=90=AF=20+=20ad?= =?UTF-8?q?b=20=E8=BF=9C=E7=A8=8B=E7=BB=88=E7=AB=AF=EF=BC=8C=E5=90=AB?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=BF=AB=E6=8D=B7=E9=80=89=E6=8B=A9/?= =?UTF-8?q?=E5=8D=B8=E8=BD=BD=20agent/=E9=87=8D=E8=BF=9E=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 6 ++ static/admin/monitor.js | 86 ++++++++++++++++++++++++- templates/admin/monitor.html | 70 ++++++++++++++++++-- web_server.py | 120 +++++++++++++++++++++++++++++++++++ 4 files changed, 276 insertions(+), 6 deletions(-) diff --git a/config.py b/config.py index 880f243..fb246d0 100644 --- a/config.py +++ b/config.py @@ -64,3 +64,9 @@ APK_DIR = os.path.join(DATA_DIR, "apks") # 启动时是否自动释放"仍被本账户占用"的设备(上次崩溃/强杀残留)。 # 仅单实例部署时设为 True:多实例共用同一 STF 账户时不要开,会误释放另一实例的运行中任务。 AUTO_RELEASE_STALE_OCCUPY = _env("AUTO_RELEASE_STALE_OCCUPY", "false").lower() in ("1", "true", "yes") + +# ================== 维护(STF 容器一键重启) ================== +# 维护页"一键重启 STF 容器"通过 SSH 到部署机执行 docker restart。 +# 前置条件:本机可免密 SSH 到目标机(stf@220 需已授权本机公钥)。 +STF_SSH_TARGET = _env("STF_SSH_TARGET", "stf@192.168.20.220") +STF_DOCKER_CONTAINER = _env("STF_DOCKER_CONTAINER", "stf") diff --git a/static/admin/monitor.js b/static/admin/monitor.js index 7f8a4cd..f7ee488 100644 --- a/static/admin/monitor.js +++ b/static/admin/monitor.js @@ -258,7 +258,7 @@ function showTab(name){ document.querySelectorAll('.tab-panel').forEach(p=>p.classList.remove('active')); document.querySelectorAll('.navbar .tab').forEach(t=>t.classList.remove('active')); document.getElementById('tab-'+name).classList.add('active'); - document.querySelectorAll('.navbar .tab')[['monitor','tasks','apps','groups','logs','users'].indexOf(name)].classList.add('active'); + document.querySelectorAll('.navbar .tab')[['monitor','tasks','apps','groups','logs','users','maintenance'].indexOf(name)].classList.add('active'); // 停止所有定时器 if(_monitorTimer){clearInterval(_monitorTimer);_monitorTimer=null;} if(_logTimer){clearInterval(_logTimer);_logTimer=null;} @@ -266,6 +266,7 @@ function showTab(name){ if(name==='monitor'){loadMonitor();_monitorTimer=setInterval(loadMonitor,5000);} if(name==='tasks'){loadTasks();loadCustomActions();} if(name==='apps')loadApks(); + if(name==='maintenance')loadAdbDevices(); if(name==='groups')loadGroups(); if(name==='logs'){loadLogs();if(document.getElementById('log-auto').checked)_logTimer=setInterval(loadLogs,3000);} if(name==='users')loadUsers(); @@ -1927,6 +1928,89 @@ async function deleteUser(uid){ handleResult(r,()=>loadUsers()); } +// ================== Tab: 维护(仅管理员) ================== +// 服务器类命令(不需要 -s 目标设备) +const _NO_DEVICE_CMDS=['devices','version','help','start-server','connect','kill-server','disconnect']; +function _selectedSerial(){ + const sel=document.getElementById('adb-device'); + return sel?sel.value:''; +} +// 选中目标设备时自动附加 -s ;服务器类命令保持原样 +function _attachDevice(cmd){ + const serial=_selectedSerial(); + if(!serial)return cmd; + if(/^-s\s/.test(cmd))return cmd; // 已手写 -s 不再重复附加 + const first=(cmd.split(/\s+/)[0]||'').toLowerCase(); + if(_NO_DEVICE_CMDS.indexOf(first)>=0)return cmd; + return '-s '+serial+' '+cmd; +} +async function loadAdbDevices(){ + const sel=document.getElementById('adb-device'); + if(!sel)return; + const r=await apiGet('/api/adb/devices'); + if(!r||!r.ok)return; + const cur=sel.value; + const stateTxt={device:'在线',offline:'离线',stf:'STF池(未连接)'}; + const opts=[''].concat( + (r.devices||[]).map(d=> + '' + ) + ); + sel.innerHTML=opts.join(''); + if(cur)sel.value=cur; +} +function fillAdbCmd(cmd){ + document.getElementById('adb-cmd-input').value=cmd; +} +async function _runAdbFull(full){ + const out=document.getElementById('adb-output'); + out.textContent='$ '+full+'\n执行中...'; + const r=await apiPost('/api/adb/cmd',{cmd:full}); + if(!r)return; + if(!r.ok){out.textContent='$ '+full+'\n⚠ '+r.error;return;} + out.textContent='$ '+full+'\n\n'+(r.stdout||'')+(r.stderr?'\n[stderr] '+r.stderr:'')+'\n\n[exit '+r.code+']'; + out.scrollTop=out.scrollHeight; +} +async function runAdbCmd(){ + const input=document.getElementById('adb-cmd-input'); + let cmd=(input.value||'').trim(); + if(!cmd){showToast('请输入 adb 命令','error');return;} + cmd=cmd.replace(/^adb\s+/i,''); // 兼容手输/快捷按钮带的 "adb " 前缀,避免拼成 "adb adb devices" + cmd=_attachDevice(cmd); + await _runAdbFull('adb '+cmd); +} +function uninstallStfAgent(){ + const serial=_selectedSerial(); + if(!serial){showToast('请先选择目标设备','error');return;} + if(!confirm('确定卸载 '+serial+' 上的 STF agent(jp.co.cyberagent.stf)?\n\n卸载后 STF 会在设备重连时自动重新推送安装。'))return; + _runAdbFull('adb -s '+serial+' uninstall jp.co.cyberagent.stf'); +} +function reconnectAdbDevice(){ + const serial=_selectedSerial(); + if(!serial){showToast('请先选择目标设备','error');return;} + _runAdbFull('adb connect '+serial); +} +function checkStfAgent(){ + const serial=_selectedSerial(); + if(!serial){showToast('请先选择目标设备','error');return;} + _runAdbFull('adb -s '+serial+' shell pm list packages | grep cyberagent'); +} +async function restartStf(){ + if(!confirm('确定重启 STF 容器?\n\n影响:STF 约 30 秒不可用,期间所有设备占用状态重置,正在执行的任务会受影响。\n\n确认继续吗?'))return; + const st=document.getElementById('stf-restart-status'); + st.textContent='正在重启 STF 容器(SSH 执行中,最长 60s)...'; + const r=await apiPost('/api/stf/restart',{}); + if(!r)return; + if(r.ok){st.textContent='✅ '+r.msg+(r.detail?' ['+r.detail+']':'');showToast(r.msg,'success');} + else{st.textContent='❌ '+r.error;showToast(r.error,'error');} +} + +// 测试此步骤弹窗关闭 +function closeTestModal(){ + const ov=document.getElementById('test-overlay'); + if(ov)ov.classList.remove('show'); +} + // ================== Tab: 应用 ================== let _apksCache=[]; let _installTimer=null; diff --git a/templates/admin/monitor.html b/templates/admin/monitor.html index c843f96..2231671 100644 --- a/templates/admin/monitor.html +++ b/templates/admin/monitor.html @@ -58,6 +58,7 @@ body{background:var(--bg);font-family:-apple-system,"Segoe UI",Roboto,"PingFang .list-pager{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:8px 0 20px} .list-pager .lp-total{font-size:12px;color:var(--text-light);margin-right:auto} .list-search{width:240px} +.adb-console{background:#0f172a;color:#e2e8f0;font-family:monospace;font-size:12px;padding:12px;border-radius:8px;min-height:200px;max-height:420px;overflow:auto;white-space:pre-wrap;word-break:break-all} /* 区块标题 */ .section-title{font-size:15px;font-weight:600;color:var(--text);margin:0 0 12px;padding-bottom:8px;border-bottom:2px solid var(--primary);display:inline-block} @@ -267,13 +268,14 @@ select.form-control{cursor:pointer}
- + - - + + +
- admin + ... 退出
@@ -464,15 +466,73 @@ select.form-control{cursor:pointer} - +
ID用户名管理员操作ID用户名管理员权限操作
+ +
+
维护
+
管理员专用:STF 服务管理与 adb 远程终端
+ +
STF 服务
+
+ + +
+
+ 通过 SSH 到部署机执行 docker restart(目标与容器名可用环境变量 + STF_SSH_TARGET / STF_DOCKER_CONTAINER 配置)。 + 影响:STF 约 30 秒不可用,期间设备占用状态重置;有任务在运行时会被拒绝执行。 +
+ +
adb 远程终端
+
选中目标设备后,命令里的 -s <设备> 自动附加,直接写 + shell ls /sdcard 即可。离线设备也可选,用"重连设备"恢复。 + 禁止 kill-server / disconnect(会中断 STF provider 的设备监控)。
+
+ + + +
+
+ + +
+
+ + + + + +
+
+ + + +
+
(执行命令后输出显示在这里)
+
+ + + +