diff --git a/static/admin/monitor.js b/static/admin/monitor.js index 217c513..7ffe374 100644 --- a/static/admin/monitor.js +++ b/static/admin/monitor.js @@ -287,14 +287,25 @@ function showTab(name){ if(_logTimer){clearInterval(_logTimer);_logTimer=null;} // 按需加载数据 if(name==='monitor'){loadMonitor();_monitorTimer=setInterval(loadMonitor,5000);} - if(name==='tasks'){loadTasks();loadCustomActions();} - if(name==='apps')loadApks(); - if(name==='maintenance'){loadAdbDevices();loadTailscaleDevices();} + if(name==='tasks'){showSubTab('tasks',_activeSubs.tasks);loadTasks();loadCustomActions();} + if(name==='apps'){showSubTab('apps',_activeSubs.apps);loadApks();} + if(name==='maintenance'){showSubTab('maintenance',_activeSubs.maintenance);loadAdbDevices();loadTailscaleDevices();} if(name==='groups')loadGroups(); if(name==='logs'){loadLogs();if(document.getElementById('log-auto').checked)_logTimer=setInterval(loadLogs,3000);} if(name==='users')loadUsers(); } +// ================== 页内子分栏(任务/应用/维护 通用) ================== +// 每个带子分栏的 Tab 记住上次选中的子分栏,切走再切回来保持原位 +let _activeSubs = {tasks: 'plan', apps: 'apks', maintenance: 'stf'}; + +function showSubTab(tabId, name){ + _activeSubs[tabId] = name; + const tab = document.getElementById('tab-'+tabId); + tab.querySelectorAll('.sub-tab').forEach(b=>b.classList.toggle('active', b.dataset.sub===name)); + tab.querySelectorAll('.sub-panel').forEach(p=>p.classList.toggle('active', p.id===tabId+'-sub-'+name)); +} + // ================== 通用列表:搜索 + 分页 ================== // 用法: // setListPager(key,{data:()=>array, filter:(item,kw)=>bool, render:(item)=>html, diff --git a/templates/admin/monitor.html b/templates/admin/monitor.html index 253620d..cdcccde 100644 --- a/templates/admin/monitor.html +++ b/templates/admin/monitor.html @@ -63,6 +63,15 @@ body{background:var(--bg);font-family:-apple-system,"Segoe UI",Roboto,"PingFang /* 区块标题 */ .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} +/* 页内子分栏(维护页用) */ +.sub-tabs{display:flex;gap:4px;margin-bottom:16px;border-bottom:2px solid var(--border)} +.sub-tab{padding:9px 16px;font-size:13px;font-weight:600;color:var(--text-muted);background:none;border:none;cursor:pointer;border-radius:8px 8px 0 0;transition:all .15s;position:relative} +.sub-tab:hover{color:var(--text);background:#f1f5f9} +.sub-tab.active{color:var(--primary);background:#eff6ff} +.sub-tab.active::after{content:"";position:absolute;left:10px;right:10px;bottom:-2px;height:2px;background:var(--primary);border-radius:1px} +.sub-panel{display:none} +.sub-panel.active{display:block} + /* 表格 */ .table{background:var(--card);border-radius:12px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.06);border:1px solid var(--border);width:100%;border-collapse:collapse;margin-bottom:24px} .table thead th{background:#f1f5f9;border-bottom:2px solid var(--border);font-weight:600;color:var(--text-muted);font-size:12px;letter-spacing:.3px;padding:12px 14px;text-align:left} @@ -334,30 +343,38 @@ select.form-control{cursor:pointer}
-
任务计划管理
-
新建、编辑、删除任务计划,支持立即执行和定时调度
-
- - - -
- - - - - -
ID任务名类型启用目标调度下次运行重试操作
-
+
任务
+
任务计划定时调度与自定义动作复用管理
- -
-
自定义动作管理
-
把常用步骤打包成可复用的动作,拖入任务步骤编辑器使用;也可在任务编辑器里"☑选中步骤→打包"生成
+
+ + +
+ + +
+
+ + + +
+ + + + + +
ID任务名类型启用目标调度下次运行重试操作
+
+
+ + +
+
把常用步骤打包成可复用的动作,拖入任务步骤编辑器使用;也可在任务编辑器里"☑选中步骤→打包"生成
@@ -368,29 +385,36 @@ select.form-control{cursor:pointer}
-
应用管理
-
上传 APK 文件,批量安装到设备(运行中设备自动跳过)
-
- - - - -
-
图标动作名步骤数创建时间操作
- - - - -
应用名包名版本大小上传时间操作
-
+
应用
+
APK 上传安装与设备已装应用查看
- -
-
设备已装应用
-
查看某台设备上安装的所有应用(含版本号)
+
+ + +
+ + +
+
+ + + + +
+ + + + + +
应用名包名版本大小上传时间操作
+
+
+ + +
- + +
+
+ + +
+
+ 通过 SSH 到部署机执行 docker restart(目标与容器名可用环境变量 + STF_SSH_TARGET / STF_DOCKER_CONTAINER 配置)。 + 影响:STF 约 30 秒不可用,期间设备占用状态重置;有任务在运行时会被拒绝执行。 +
-
- - -
-
- - - - - -
-
- - - -
-
(执行命令后输出显示在这里)
-
Tailscale 管理
-
-
- - - - + +
+
选中目标设备后,命令里的 -s <设备> 自动附加,直接写 + shell ls /sdcard 即可。离线设备也可选,用"重连设备"恢复。 + 禁止 kill-server / disconnect(会中断 STF provider 的设备监控)。
+
+ + + +
+
+ + +
+
+ + + + + +
+
+ + + +
+
(执行命令后输出显示在这里)
+
+ + +
+
+
+ + + + +
+ + + + + +
主机名显示名IP系统在线授权密钥过期操作
点击"刷新设备"加载
+
+
"设置IP"可修改设备 IPv4(会断开当前连接,平台 serial 随之变化,需同步 STF 设备池);"密钥不过期"= 关闭设备密钥验证,设备不会被定期踢下线;auth key 生成后只显示一次,请立即复制。
- - - - - -
主机名显示名IP系统在线授权密钥过期操作
点击"刷新设备"加载
-
-
"设置IP"可修改设备 IPv4(会断开当前连接,平台 serial 随之变化,需同步 STF 设备池);"密钥不过期"= 关闭设备密钥验证,设备不会被定期踢下线;auth key 生成后只显示一次,请立即复制。
diff --git a/web_server.py b/web_server.py index bec2788..928f5e9 100644 --- a/web_server.py +++ b/web_server.py @@ -49,6 +49,8 @@ _web_secret = os.environ.get("WEB_SECRET_KEY") or secrets.token_hex(32) if not os.environ.get("WEB_SECRET_KEY"): _log.warning("未配置 WEB_SECRET_KEY,已随机生成会话密钥(重启后登录态失效)") app.config["SECRET_KEY"] = _web_secret +# debug=False 时模板默认不自动重载,开发期改 HTML 需重启才生效;这里显式开启 +app.config["TEMPLATES_AUTO_RELOAD"] = True app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + os.path.join( os.path.dirname(os.path.abspath(__file__)), "data", "users.db") app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False