feat: 任务/应用/维护页统一页内子分栏(showSubTab 泛化),维护页首拆子分栏;模板自动重载(TEMPLATES_AUTO_RELOAD)
This commit is contained in:
+14
-3
@@ -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,
|
||||
|
||||
+128
-92
@@ -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}
|
||||
|
||||
<!-- ========== Tab 2: 任务 ========== -->
|
||||
<div id="tab-tasks" class="tab-panel">
|
||||
<div class="page-title">任务计划管理</div>
|
||||
<div class="page-sub">新建、编辑、删除任务计划,支持立即执行和定时调度</div>
|
||||
<div class="toolbar">
|
||||
<button class="btn btn-primary" onclick="openTaskModal()" data-perm="tasks">新建任务</button>
|
||||
<button class="btn" onclick="loadTasks()">刷新</button>
|
||||
<input class="form-control list-search" id="search-tasks" placeholder="搜索任务名/类型/ID...">
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>ID</th><th>任务名</th><th>类型</th><th>启用</th><th>目标</th><th>调度</th><th>下次运行</th><th>重试</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-tasks"></tbody>
|
||||
</table>
|
||||
<div class="list-pager" id="pager-tasks"></div>
|
||||
<div class="page-title">任务</div>
|
||||
<div class="page-sub">任务计划定时调度与自定义动作复用管理</div>
|
||||
|
||||
<!-- 自定义动作管理 -->
|
||||
<div style="margin-top:28px">
|
||||
<div class="page-title" style="font-size:16px">自定义动作管理</div>
|
||||
<div class="page-sub">把常用步骤打包成可复用的动作,拖入任务步骤编辑器使用;也可在任务编辑器里"☑选中步骤→打包"生成</div>
|
||||
<div class="sub-tabs">
|
||||
<button class="sub-tab active" data-sub="plan" onclick="showSubTab('tasks','plan')">任务计划</button>
|
||||
<button class="sub-tab" data-sub="actions" onclick="showSubTab('tasks','actions')">自定义动作</button>
|
||||
</div>
|
||||
|
||||
<!-- 子分栏 1:任务计划 -->
|
||||
<div class="sub-panel active" id="tasks-sub-plan">
|
||||
<div class="toolbar">
|
||||
<button class="btn btn-primary" onclick="openTaskModal()" data-perm="tasks">新建任务</button>
|
||||
<button class="btn" onclick="loadTasks()">刷新</button>
|
||||
<input class="form-control list-search" id="search-tasks" placeholder="搜索任务名/类型/ID...">
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>ID</th><th>任务名</th><th>类型</th><th>启用</th><th>目标</th><th>调度</th><th>下次运行</th><th>重试</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-tasks"></tbody>
|
||||
</table>
|
||||
<div class="list-pager" id="pager-tasks"></div>
|
||||
</div>
|
||||
|
||||
<!-- 子分栏 2:自定义动作 -->
|
||||
<div class="sub-panel" id="tasks-sub-actions">
|
||||
<div class="toolbar">
|
||||
<button class="btn btn-primary" onclick="openActionModal()" data-perm="tasks">新建动作</button>
|
||||
<button class="btn" onclick="loadCustomActions()">刷新</button>
|
||||
<input class="form-control list-search" id="search-actions" placeholder="搜索动作名...">
|
||||
</div>
|
||||
<div class="help">把常用步骤打包成可复用的动作,拖入任务步骤编辑器使用;也可在任务编辑器里"☑选中步骤→打包"生成</div>
|
||||
<table class="table">
|
||||
<thead><tr><th>图标</th><th>动作名</th><th>步骤数</th><th>创建时间</th><th>操作</th></tr></thead>
|
||||
<tbody id="tb-actions"></tbody>
|
||||
@@ -368,29 +385,36 @@ select.form-control{cursor:pointer}
|
||||
|
||||
<!-- ========== Tab: 应用 ========== -->
|
||||
<div id="tab-apps" class="tab-panel">
|
||||
<div class="page-title">应用管理</div>
|
||||
<div class="page-sub">上传 APK 文件,批量安装到设备(运行中设备自动跳过)</div>
|
||||
<div class="toolbar">
|
||||
<label class="btn btn-primary" style="cursor:pointer;margin-bottom:0">
|
||||
上传APK
|
||||
<input type="file" accept=".apk" style="display:none" onchange="uploadApk(this)">
|
||||
</label>
|
||||
<button class="btn" onclick="loadApks()">刷新</button>
|
||||
<input class="form-control list-search" id="search-apks" placeholder="搜索应用名/包名...">
|
||||
<span class="hint" id="apk-hint"></span>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>应用名</th><th>包名</th><th>版本</th><th>大小</th><th>上传时间</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-apks"></tbody>
|
||||
</table>
|
||||
<div class="list-pager" id="pager-apks"></div>
|
||||
<div class="page-title">应用</div>
|
||||
<div class="page-sub">APK 上传安装与设备已装应用查看</div>
|
||||
|
||||
<!-- 设备已装应用 -->
|
||||
<div style="margin-top:28px">
|
||||
<div class="page-title" style="font-size:16px">设备已装应用</div>
|
||||
<div class="page-sub">查看某台设备上安装的所有应用(含版本号)</div>
|
||||
<div class="sub-tabs">
|
||||
<button class="sub-tab active" data-sub="apks" onclick="showSubTab('apps','apks')">应用管理</button>
|
||||
<button class="sub-tab" data-sub="devapps" onclick="showSubTab('apps','devapps')">设备已装应用</button>
|
||||
</div>
|
||||
|
||||
<!-- 子分栏 1:应用管理 -->
|
||||
<div class="sub-panel active" id="apps-sub-apks">
|
||||
<div class="toolbar">
|
||||
<label class="btn btn-primary" style="cursor:pointer;margin-bottom:0">
|
||||
上传APK
|
||||
<input type="file" accept=".apk" style="display:none" onchange="uploadApk(this)">
|
||||
</label>
|
||||
<button class="btn" onclick="loadApks()">刷新</button>
|
||||
<input class="form-control list-search" id="search-apks" placeholder="搜索应用名/包名...">
|
||||
<span class="hint" id="apk-hint"></span>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>应用名</th><th>包名</th><th>版本</th><th>大小</th><th>上传时间</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-apks"></tbody>
|
||||
</table>
|
||||
<div class="list-pager" id="pager-apks"></div>
|
||||
</div>
|
||||
|
||||
<!-- 子分栏 2:设备已装应用 -->
|
||||
<div class="sub-panel" id="apps-sub-devapps">
|
||||
<div class="toolbar">
|
||||
<select id="apps-device" class="form-control" style="width:280px;display:inline-block">
|
||||
<option value="">选择设备...</option>
|
||||
@@ -476,64 +500,76 @@ select.form-control{cursor:pointer}
|
||||
<!-- ========== Tab 7: 维护(仅管理员) ========== -->
|
||||
<div id="tab-maintenance" class="tab-panel">
|
||||
<div class="page-title">维护</div>
|
||||
<div class="page-sub">管理员专用:STF 服务管理与 adb 远程终端</div>
|
||||
<div class="page-sub">管理员专用:STF 服务、adb 远程终端与 Tailscale 设备管理</div>
|
||||
|
||||
<div class="section-title">STF 服务</div>
|
||||
<div class="toolbar">
|
||||
<button class="btn btn-danger" onclick="restartStf()">一键重启 STF 容器</button>
|
||||
<span id="stf-restart-status" class="text-muted" style="margin-left:10px"></span>
|
||||
</div>
|
||||
<div class="help" style="margin-top:8px">
|
||||
通过 SSH 到部署机执行 <code>docker restart</code>(目标与容器名可用环境变量
|
||||
<code>STF_SSH_TARGET</code> / <code>STF_DOCKER_CONTAINER</code> 配置)。
|
||||
影响:STF 约 30 秒不可用,期间设备占用状态重置;<b>有任务在运行时会被拒绝执行</b>。
|
||||
<div class="sub-tabs">
|
||||
<button class="sub-tab active" data-sub="stf" onclick="showSubTab('maintenance','stf')">STF 服务</button>
|
||||
<button class="sub-tab" data-sub="adb" onclick="showSubTab('maintenance','adb')">adb 远程终端</button>
|
||||
<button class="sub-tab" data-sub="ts" onclick="showSubTab('maintenance','ts')">Tailscale 管理</button>
|
||||
</div>
|
||||
|
||||
<div class="section-title" style="margin-top:16px">adb 远程终端</div>
|
||||
<div class="help">选中目标设备后,命令里的 <code>-s <设备></code> 自动附加,直接写
|
||||
<code>shell ls /sdcard</code> 即可。离线设备也可选,用"重连设备"恢复。
|
||||
<b>禁止 kill-server / disconnect</b>(会中断 STF provider 的设备监控)。</div>
|
||||
<div style="display:flex;gap:8px;margin:8px 0;align-items:center">
|
||||
<label style="font-size:12px;color:var(--text-light)">目标设备</label>
|
||||
<select id="adb-device" class="form-control" style="flex:1;max-width:340px;font-family:monospace"></select>
|
||||
<button class="btn btn-xs" onclick="loadAdbDevices()" title="刷新设备列表">刷新</button>
|
||||
<!-- 子分栏 1:STF 服务 -->
|
||||
<div class="sub-panel active" id="maintenance-sub-stf">
|
||||
<div class="toolbar">
|
||||
<button class="btn btn-danger" onclick="restartStf()">一键重启 STF 容器</button>
|
||||
<span id="stf-restart-status" class="text-muted"></span>
|
||||
</div>
|
||||
<div class="help" style="margin-top:8px">
|
||||
通过 SSH 到部署机执行 <code>docker restart</code>(目标与容器名可用环境变量
|
||||
<code>STF_SSH_TARGET</code> / <code>STF_DOCKER_CONTAINER</code> 配置)。
|
||||
影响:STF 约 30 秒不可用,期间设备占用状态重置;<b>有任务在运行时会被拒绝执行</b>。
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;margin:8px 0">
|
||||
<input type="text" id="adb-cmd-input" class="form-control" style="flex:1;font-family:monospace"
|
||||
placeholder="shell ls /sdcard(选中设备后 -s 自动附加)"
|
||||
onkeydown="if(event.key==='Enter')runAdbCmd()">
|
||||
<button class="btn btn-primary" onclick="runAdbCmd()">执行</button>
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap">
|
||||
<button class="btn btn-xs" onclick="fillAdbCmd('adb devices')">adb devices</button>
|
||||
<button class="btn btn-xs" onclick="fillAdbCmd('shell dumpsys window | grep mCurrentFocus')">前台App</button>
|
||||
<button class="btn btn-xs" onclick="fillAdbCmd('shell ls /sdcard')">列文件</button>
|
||||
<button class="btn btn-xs" onclick="fillAdbCmd('shell screencap -p /sdcard/shot.png')">截屏</button>
|
||||
<button class="btn btn-xs" onclick="fillAdbCmd('shell pm list packages -3')">已装应用</button>
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap">
|
||||
<button class="btn btn-xs btn-danger" onclick="uninstallStfAgent()" title="卸载 jp.co.cyberagent.stf,STF 会在设备重连时重新推送">卸载 STF agent(卡死时用)</button>
|
||||
<button class="btn btn-xs" onclick="reconnectAdbDevice()" title="重新 adb connect 该设备">重连设备</button>
|
||||
<button class="btn btn-xs" onclick="checkStfAgent()" title="查看 jp.co.cyberagent.stf 是否已安装">检查 agent</button>
|
||||
</div>
|
||||
<pre id="adb-output" class="adb-console">(执行命令后输出显示在这里)</pre>
|
||||
|
||||
<div class="section-title" style="margin-top:16px">Tailscale 管理</div>
|
||||
<div class="help" id="ts-hint"></div>
|
||||
<div class="toolbar" style="margin:8px 0">
|
||||
<button class="btn btn-primary" onclick="loadTailscaleDevices()">刷新设备</button>
|
||||
<button class="btn" onclick="openTailscaleAuthKey()">生成接入 Auth Key</button>
|
||||
<input class="form-control list-search" id="search-tailscale" placeholder="搜索主机名/名称/IP...">
|
||||
<span id="ts-status" class="text-muted" style="margin-left:10px"></span>
|
||||
<!-- 子分栏 2:adb 远程终端 -->
|
||||
<div class="sub-panel" id="maintenance-sub-adb">
|
||||
<div class="help">选中目标设备后,命令里的 <code>-s <设备></code> 自动附加,直接写
|
||||
<code>shell ls /sdcard</code> 即可。离线设备也可选,用"重连设备"恢复。
|
||||
<b>禁止 kill-server / disconnect</b>(会中断 STF provider 的设备监控)。</div>
|
||||
<div style="display:flex;gap:8px;margin:8px 0;align-items:center">
|
||||
<label style="font-size:12px;color:var(--text-light)">目标设备</label>
|
||||
<select id="adb-device" class="form-control" style="flex:1;max-width:340px;font-family:monospace"></select>
|
||||
<button class="btn btn-xs" onclick="loadAdbDevices()" title="刷新设备列表">刷新</button>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;margin:8px 0">
|
||||
<input type="text" id="adb-cmd-input" class="form-control" style="flex:1;font-family:monospace"
|
||||
placeholder="shell ls /sdcard(选中设备后 -s 自动附加)"
|
||||
onkeydown="if(event.key==='Enter')runAdbCmd()">
|
||||
<button class="btn btn-primary" onclick="runAdbCmd()">执行</button>
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap">
|
||||
<button class="btn btn-xs" onclick="fillAdbCmd('adb devices')">adb devices</button>
|
||||
<button class="btn btn-xs" onclick="fillAdbCmd('shell dumpsys window | grep mCurrentFocus')">前台App</button>
|
||||
<button class="btn btn-xs" onclick="fillAdbCmd('shell ls /sdcard')">列文件</button>
|
||||
<button class="btn btn-xs" onclick="fillAdbCmd('shell screencap -p /sdcard/shot.png')">截屏</button>
|
||||
<button class="btn btn-xs" onclick="fillAdbCmd('shell pm list packages -3')">已装应用</button>
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap">
|
||||
<button class="btn btn-xs btn-danger" onclick="uninstallStfAgent()" title="卸载 jp.co.cyberagent.stf,STF 会在设备重连时重新推送">卸载 STF agent(卡死时用)</button>
|
||||
<button class="btn btn-xs" onclick="reconnectAdbDevice()" title="重新 adb connect 该设备">重连设备</button>
|
||||
<button class="btn btn-xs" onclick="checkStfAgent()" title="查看 jp.co.cyberagent.stf 是否已安装">检查 agent</button>
|
||||
</div>
|
||||
<pre id="adb-output" class="adb-console">(执行命令后输出显示在这里)</pre>
|
||||
</div>
|
||||
|
||||
<!-- 子分栏 3:Tailscale 管理 -->
|
||||
<div class="sub-panel" id="maintenance-sub-ts">
|
||||
<div class="help" id="ts-hint"></div>
|
||||
<div class="toolbar" style="margin:8px 0">
|
||||
<button class="btn btn-primary" onclick="loadTailscaleDevices()">刷新设备</button>
|
||||
<button class="btn" onclick="openTailscaleAuthKey()">生成接入 Auth Key</button>
|
||||
<input class="form-control list-search" id="search-tailscale" placeholder="搜索主机名/名称/IP...">
|
||||
<span id="ts-status" class="text-muted" style="margin-left:10px"></span>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>主机名</th><th>显示名</th><th>IP</th><th>系统</th><th>在线</th><th>授权</th><th>密钥过期</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-tailscale"><tr><td colspan="8" class="empty">点击"刷新设备"加载</td></tr></tbody>
|
||||
</table>
|
||||
<div class="list-pager" id="pager-tailscale"></div>
|
||||
<div class="help">"设置IP"可修改设备 IPv4(会断开当前连接,平台 serial 随之变化,需同步 STF 设备池);"密钥不过期"= 关闭设备密钥验证,设备不会被定期踢下线;auth key 生成后只显示一次,请立即复制。</div>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>主机名</th><th>显示名</th><th>IP</th><th>系统</th><th>在线</th><th>授权</th><th>密钥过期</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-tailscale"><tr><td colspan="8" class="empty">点击"刷新设备"加载</td></tr></tbody>
|
||||
</table>
|
||||
<div class="list-pager" id="pager-tailscale"></div>
|
||||
<div class="help">"设置IP"可修改设备 IPv4(会断开当前连接,平台 serial 随之变化,需同步 STF 设备池);"密钥不过期"= 关闭设备密钥验证,设备不会被定期踢下线;auth key 生成后只显示一次,请立即复制。</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /.content -->
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user