refactor: 列表统一为 setListPager 搜索+分页+排序(_lpSort 升序/降序/取消,中文数字感知,空值排后);监控设备表并入统一排序;STF设备管理/应用版本管理接入统一列表
This commit is contained in:
+120
-77
@@ -60,31 +60,9 @@ async function apiPut(url,body){
|
||||
}
|
||||
|
||||
// ================== 设备列表排序与多选 ==================
|
||||
let _sortKey=''; // 当前排序字段
|
||||
let _sortDir=''; // '' | 'asc' | 'desc'
|
||||
let _selectedSerials=new Set(); // 已勾选的 serial 集合
|
||||
|
||||
// 点击表头排序
|
||||
function sortBy(key){
|
||||
if(_sortKey===key){
|
||||
// 同一列:切换方向;已是降序则取消
|
||||
if(_sortDir==='asc'){_sortDir='desc';}
|
||||
else if(_sortDir==='desc'){_sortKey='';_sortDir='';}
|
||||
else{_sortDir='asc';}
|
||||
}else{
|
||||
_sortKey=key;_sortDir='asc';
|
||||
}
|
||||
// 更新表头样式
|
||||
document.querySelectorAll('.table thead th.sortable').forEach(th=>{
|
||||
th.classList.remove('sort-asc','sort-desc');
|
||||
if(th.dataset.key===_sortKey){
|
||||
th.classList.add(_sortDir==='asc'?'sort-asc':(_sortDir==='desc'?'sort-desc':''));
|
||||
}
|
||||
});
|
||||
renderDevices();
|
||||
}
|
||||
|
||||
// 排序比较:从设备对象取排序值
|
||||
// 排序比较:从设备对象取排序值(统一排序用,见 setListPager/sortValue)
|
||||
function _sortValue(dev,key){
|
||||
let v;
|
||||
if(key==='progress'){
|
||||
@@ -351,6 +329,8 @@ function toggleClipDevice(cb){
|
||||
}
|
||||
|
||||
// ================== 工具:应用版本管理 ==================
|
||||
let _appVerRows = []; // [{serial, installed, version_name, version_code, error}]
|
||||
|
||||
async function queryAppVers(){
|
||||
const pkg = document.getElementById('appver-pkg').value.trim();
|
||||
if(!pkg){showToast('请输入包名','error');return;}
|
||||
@@ -359,24 +339,41 @@ async function queryAppVers(){
|
||||
try{
|
||||
const r = await apiPost('/api/tools/appver', {pkg});
|
||||
if(!r || !r.ok){showToast('查询失败: '+(r&&r.error||''),'error');return;}
|
||||
const rows = Object.entries(r.results||{}).map(([serial,v])=>{
|
||||
if(v.error)return '<tr><td style="font-family:monospace">'+esc(serial)+'</td>'+
|
||||
'<td><span class="label label-warning">查询失败</span></td><td colspan="2">'+esc(v.error)+'</td></tr>';
|
||||
if(v.installed)return '<tr><td style="font-family:monospace">'+esc(serial)+'</td>'+
|
||||
'<td><span class="label label-success">已安装</span></td>'+
|
||||
'<td>'+esc(v.version_name||'')+'</td><td style="font-family:monospace">'+esc(v.version_code||'')+'</td></tr>';
|
||||
return '<tr><td style="font-family:monospace">'+esc(serial)+'</td>'+
|
||||
'<td><span class="label label-default">未安装</span></td><td>-</td><td>-</td></tr>';
|
||||
}).join('');
|
||||
document.getElementById('tb-appver').innerHTML = rows || '<tr><td colspan="4" class="empty">无可用设备</td></tr>';
|
||||
_appVerRows = Object.entries(r.results||{}).map(([serial,v])=>({serial, ...v}));
|
||||
setListPager('appver',{
|
||||
data:()=>_appVerRows,
|
||||
filter:(x,kw)=>((x.serial||'').toLowerCase().includes(kw)||(x.version_name||'').toLowerCase().includes(kw)||(x.version_code||'').toLowerCase().includes(kw)),
|
||||
sortValue:(x,k)=>{
|
||||
if(k==='state'){ if(x.error)return 2; return x.installed?0:1; }
|
||||
if(k==='code')return parseInt(x.version_code)||0;
|
||||
if(k==='version')return x.version_name||'';
|
||||
return x.serial||'';
|
||||
},
|
||||
render:renderAppVerRow,
|
||||
tbody:'tb-appver',pager:'pager-appver',pageSize:10,
|
||||
empty:'<tr><td colspan="4" class="empty">无可用设备</td></tr>'
|
||||
});
|
||||
applyListPager('appver');
|
||||
initListSearch('appver','search-appver');
|
||||
document.getElementById('appver-status').textContent = '共 '+(r.total||0)+' 台设备'+(r.fail?','+r.fail+' 台查询失败':'');
|
||||
if(r.fail)showToast(r.fail+' 台设备查询失败,详见表格','error');
|
||||
} finally {
|
||||
btn.disabled = false; btn.textContent = old;
|
||||
}
|
||||
}
|
||||
function renderAppVerRow(x){
|
||||
if(x.error)return '<tr><td style="font-family:monospace">'+esc(x.serial)+'</td>'+
|
||||
'<td><span class="label label-warning">查询失败</span></td><td colspan="2">'+esc(x.error)+'</td></tr>';
|
||||
if(x.installed)return '<tr><td style="font-family:monospace">'+esc(x.serial)+'</td>'+
|
||||
'<td><span class="label label-success">已安装</span></td>'+
|
||||
'<td>'+esc(x.version_name||'')+'</td><td style="font-family:monospace">'+esc(x.version_code||'')+'</td></tr>';
|
||||
return '<tr><td style="font-family:monospace">'+esc(x.serial)+'</td>'+
|
||||
'<td><span class="label label-default">未安装</span></td><td>-</td><td>-</td></tr>';
|
||||
}
|
||||
|
||||
// ================== 工具:STF 设备管理 ==================
|
||||
let _stfDevRows = []; // [{ip, inCfg, state}]
|
||||
|
||||
async function loadStfDevMgmt(){
|
||||
const status = document.getElementById('stfdev-status');
|
||||
const r = await apiGet('/api/stf/devmgmt/status');
|
||||
@@ -387,26 +384,34 @@ async function loadStfDevMgmt(){
|
||||
const s = d.serial;
|
||||
connected[s.endsWith(':5555') ? s.slice(0,-5) : s] = d.state;
|
||||
});
|
||||
const ips = Array.from(new Set(configured.concat(Object.keys(connected))));
|
||||
const tbody = document.getElementById('tb-stfdev');
|
||||
if(!ips.length){
|
||||
tbody.innerHTML='<tr><td colspan="4" class="empty">无设备(脚本未配置、220 adb 也无连接)</td></tr>';
|
||||
status.textContent='';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = ips.map(ip=>{
|
||||
const inCfg = configured.includes(ip);
|
||||
const st = connected[ip];
|
||||
const stHtml = st==='device'
|
||||
? '<span class="label label-success">已连接</span>'
|
||||
: (st ? '<span class="label label-warning">'+esc(st)+'</span>' : '<span class="label label-default">未连接</span>');
|
||||
return '<tr><td style="font-family:monospace">'+esc(ip)+'</td>'+
|
||||
'<td>'+(inCfg?'<span class="label label-info">脚本中</span>':'<span class="label label-default">未配置</span>')+'</td>'+
|
||||
'<td>'+stHtml+'</td>'+
|
||||
'<td><button class="btn btn-xs btn-danger" onclick="removeStfDev(\''+esc(ip)+'\')">移除(断开+删脚本)</button></td></tr>';
|
||||
}).join('');
|
||||
_stfDevRows = Array.from(new Set(configured.concat(Object.keys(connected)))).map(ip=>({
|
||||
ip, inCfg: configured.includes(ip), state: connected[ip]||''
|
||||
}));
|
||||
setListPager('stfdev',{
|
||||
data:()=>_stfDevRows,
|
||||
filter:(x,kw)=>x.ip.toLowerCase().includes(kw),
|
||||
sortValue:(x,k)=>{
|
||||
if(k==='cfg')return x.inCfg?1:0;
|
||||
if(k==='state'){ const o={device:0,offline:1,unauthorized:2,'':9}; return (o[x.state]!==undefined)?o[x.state]:9; }
|
||||
return x.ip||'';
|
||||
},
|
||||
render:renderStfDevRow,
|
||||
tbody:'tb-stfdev',pager:'pager-stfdev',pageSize:10,
|
||||
empty:'<tr><td colspan="4" class="empty">无设备(脚本未配置、220 adb 也无连接)</td></tr>'
|
||||
});
|
||||
applyListPager('stfdev');
|
||||
initListSearch('stfdev','search-stfdev');
|
||||
status.textContent = '脚本配置 '+configured.length+' 台,已连接 '+Object.keys(connected).length+' 台';
|
||||
}
|
||||
function renderStfDevRow(x){
|
||||
const stHtml = x.state==='device'
|
||||
? '<span class="label label-success">已连接</span>'
|
||||
: (x.state ? '<span class="label label-warning">'+esc(x.state)+'</span>' : '<span class="label label-default">未连接</span>');
|
||||
return '<tr><td style="font-family:monospace">'+esc(x.ip)+'</td>'+
|
||||
'<td>'+(x.inCfg?'<span class="label label-info">脚本中</span>':'<span class="label label-default">未配置</span>')+'</td>'+
|
||||
'<td>'+stHtml+'</td>'+
|
||||
'<td><button class="btn btn-xs btn-danger" onclick="removeStfDev(\''+esc(x.ip)+'\')">移除(断开+删脚本)</button></td></tr>';
|
||||
}
|
||||
|
||||
async function addStfDev(){
|
||||
const ip = document.getElementById('stfdev-ip').value.trim();
|
||||
@@ -449,18 +454,22 @@ async function injectClipboard(){
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 通用列表:搜索 + 分页 ==================
|
||||
// ================== 通用列表:搜索 + 分页 + 排序 ==================
|
||||
// 用法:
|
||||
// setListPager(key,{data:()=>array, filter:(item,kw)=>bool, render:(item)=>html,
|
||||
// tbody:'tb-id', pager:'pager-id', pageSize:N});
|
||||
// applyListPager(key); // 数据变化后重渲染(含搜索/翻页状态)
|
||||
// initListSearch(key,'search-id');// 搜索框绑定
|
||||
// tbody:'tb-id', pager:'pager-id', pageSize:N,
|
||||
// sortValue:(item,colKey)=>值, // 可排序列的取值函数
|
||||
// empty:'<tr>...'});
|
||||
// applyListPager(key); // 数据变化后重渲染(含搜索/翻页/排序状态)
|
||||
// initListSearch(key,'search-id'); // 搜索框绑定
|
||||
// _lpSort(key,'colKey'); // 表头排序:th 加 data-sort="colKey" + onclick,
|
||||
// // 再点切换升降序,第三次取消;排序指示器自动更新
|
||||
var _LIST_PAGERS={};
|
||||
|
||||
function _lp(key){ return _LIST_PAGERS[key] || (_LIST_PAGERS[key]={page:1,kw:'',cfg:null}); }
|
||||
function _lp(key){ return _LIST_PAGERS[key] || (_LIST_PAGERS[key]={page:1,kw:'',cfg:null,sort:null}); }
|
||||
|
||||
function setListPager(key,cfg){
|
||||
// 只更新配置,不重置 page/kw —— 监控列表 5 秒自动刷新时保持用户当前页
|
||||
// 只更新配置,不重置 page/kw/sort —— 监控列表 5 秒自动刷新时保持用户当前状态
|
||||
var st=_lp(key); st.cfg=cfg;
|
||||
}
|
||||
function initListSearch(key,inputId){
|
||||
@@ -471,6 +480,17 @@ function initListSearch(key,inputId){
|
||||
var st=_lp(key); st.kw=this.value.trim(); st.page=1; applyListPager(key);
|
||||
});
|
||||
}
|
||||
function _lpSort(key,colKey){
|
||||
var st=_lp(key);
|
||||
if(st.sort&&st.sort.key===colKey){
|
||||
if(st.sort.dir===1)st.sort.dir=-1; // 升序 → 降序
|
||||
else if(st.sort.dir===-1)st.sort=null; // 降序 → 取消排序
|
||||
else st.sort={key:colKey,dir:1};
|
||||
}else{
|
||||
st.sort={key:colKey,dir:1};
|
||||
}
|
||||
applyListPager(key);
|
||||
}
|
||||
function applyListPager(key){
|
||||
var st=_lp(key);
|
||||
if(!st.cfg)return;
|
||||
@@ -478,6 +498,18 @@ function applyListPager(key){
|
||||
var all=(cfg.data&&cfg.data())||[];
|
||||
var kw=st.kw.toLowerCase();
|
||||
var filtered=kw?all.filter(function(x){return cfg.filter(x,kw);}):all;
|
||||
// 排序:cfg.sortValue 取列值;空值/缺失恒排最后;字符串用中文数字感知比较(10 排在 9 后)
|
||||
if(st.sort&&cfg.sortValue){
|
||||
var sk=st.sort.key, dir=st.sort.dir;
|
||||
filtered=filtered.slice().sort(function(a,b){
|
||||
var va=cfg.sortValue(a,sk), vb=cfg.sortValue(b,sk);
|
||||
if(va===vb)return 0;
|
||||
if(va==null||va==='')return 1;
|
||||
if(vb==null||vb==='')return -1;
|
||||
if(typeof va==='number'&&typeof vb==='number')return (va-vb)*dir;
|
||||
return String(va).localeCompare(String(vb),'zh-Hans-CN',{numeric:true})*dir;
|
||||
});
|
||||
}
|
||||
var pageSize=cfg.pageSize||10;
|
||||
var pages=Math.max(1,Math.ceil(filtered.length/pageSize));
|
||||
if(st.page>pages)st.page=pages;
|
||||
@@ -485,7 +517,18 @@ function applyListPager(key){
|
||||
var start=(st.page-1)*pageSize;
|
||||
var slice=filtered.slice(start,start+pageSize);
|
||||
var tb=document.getElementById(cfg.tbody);
|
||||
if(tb)tb.innerHTML=slice.map(cfg.render).join('')||(cfg.empty||'<tr><td colspan="20" class="empty">无数据</td></tr>');
|
||||
if(tb){
|
||||
tb.innerHTML=slice.map(cfg.render).join('')||(cfg.empty||'<tr><td colspan="20" class="empty">无数据</td></tr>');
|
||||
// 表头排序指示器(th[data-sort],所有表格统一)
|
||||
if(cfg.sortValue){
|
||||
var table=tb.closest('table');
|
||||
if(table)table.querySelectorAll('thead th[data-sort]').forEach(function(th){
|
||||
th.classList.add('sortable');
|
||||
th.classList.toggle('sort-asc',!!st.sort&&st.sort.key===th.dataset.sort&&st.sort.dir===1);
|
||||
th.classList.toggle('sort-desc',!!st.sort&&st.sort.key===th.dataset.sort&&st.sort.dir===-1);
|
||||
});
|
||||
}
|
||||
}
|
||||
var pg=document.getElementById(cfg.pager);
|
||||
if(pg)pg.innerHTML=_lpPagerHtml(key,pages,st.page,filtered.length,pageSize);
|
||||
}
|
||||
@@ -595,33 +638,20 @@ async function loadSummary(){
|
||||
}catch(e){console.error(e);}
|
||||
}
|
||||
|
||||
// 渲染设备表格(带排序+搜索+分页,复选状态保持)
|
||||
let _sortedDevices=[];
|
||||
// 渲染设备表格(统一排序+搜索+分页,复选状态保持)
|
||||
let _devicesSig='';
|
||||
function renderDevices(){
|
||||
if(!_statusData||!_statusData.devices){return;}
|
||||
// 5秒自动刷新时若设备数据/分页/排序都没变化,跳过整表重渲染(避免DOM抖动)
|
||||
// 5秒自动刷新时若设备数据/分页/搜索/排序都没变化,跳过整表重渲染(避免DOM抖动)
|
||||
const _st=_lp('devices');
|
||||
const _sig=JSON.stringify({d:_statusData.devices,p:_st.page,k:_st.kw,s:_sortKey+(_sortDir||'')});
|
||||
const _sig=JSON.stringify({d:_statusData.devices,p:_st.page,k:_st.kw,s:_st.sort});
|
||||
if(_sig===_devicesSig)return;
|
||||
_devicesSig=_sig;
|
||||
let devices=_statusData.devices.slice();
|
||||
// 排序
|
||||
if(_sortKey&&_sortDir){
|
||||
const dir=_sortDir==='asc'?1:-1;
|
||||
devices.sort((a,b)=>{
|
||||
let va=_sortValue(a,_sortKey),vb=_sortValue(b,_sortKey);
|
||||
if(typeof va==='string'&&typeof vb==='string'){
|
||||
return va.localeCompare(vb)*dir;
|
||||
}
|
||||
va=Number(va)||0;vb=Number(vb)||0;
|
||||
return (va-vb)*dir;
|
||||
});
|
||||
}
|
||||
_sortedDevices=devices;
|
||||
// 排序由 applyListPager 统一处理(sortValue + _lpSort)
|
||||
setListPager('devices',{
|
||||
data:()=>_sortedDevices,
|
||||
data:()=>_statusData.devices||[],
|
||||
filter:(dev,kw)=>((dev.serial||'').toLowerCase().includes(kw)||(dev.model||'').toLowerCase().includes(kw)||(dev.foreground_app||'').toLowerCase().includes(kw)||(dev.task_job||'').toLowerCase().includes(kw)||(dev.current_action||'').toLowerCase().includes(kw)),
|
||||
sortValue:_sortValue,
|
||||
render:renderDeviceRow,
|
||||
tbody:'tb-devices',pager:'pager-devices',pageSize:10,
|
||||
empty:'<tr><td colspan="11" class="empty">暂无设备</td></tr>'
|
||||
@@ -804,6 +834,7 @@ function renderTaskTable(){
|
||||
setListPager('tasks',{
|
||||
data:()=>jobs,
|
||||
filter:(j,kw)=>((j.name||'').toLowerCase().includes(kw)||(j.task_type||'').toLowerCase().includes(kw)||(j.id||'').toLowerCase().includes(kw)),
|
||||
sortValue:(j,k)=>{ if(k==='id')return j.id||''; if(k==='enabled')return j.enabled?1:0; if(k==='next_run')return j.next_run||''; return j[k]||''; },
|
||||
render:renderTaskRow,
|
||||
tbody:'tb-tasks',pager:'pager-tasks',pageSize:10,
|
||||
empty:'<tr><td colspan="9" class="empty">暂无任务,点击"新建任务"创建</td></tr>'
|
||||
@@ -2124,6 +2155,7 @@ async function loadCustomActions(){
|
||||
setListPager('actions',{
|
||||
data:()=>_customActions,
|
||||
filter:(a,kw)=>((a.name||'').toLowerCase().includes(kw)),
|
||||
sortValue:(a,k)=>{ if(k==='steps')return (a.steps||[]).length; if(k==='created_at')return a.created_at||''; return a[k]||''; },
|
||||
render:renderActionRow,
|
||||
tbody:'tb-actions',pager:'pager-actions',pageSize:10,
|
||||
empty:'<tr><td colspan="5" class="empty">暂无自定义动作,可点击"新建动作"或用任务编辑器☑选中步骤打包</td></tr>'
|
||||
@@ -2211,6 +2243,7 @@ function renderGroupTable(){
|
||||
setListPager('groups',{
|
||||
data:()=>groups,
|
||||
filter:(g,kw)=>((g.name||'').toLowerCase().includes(kw)||(g.description||'').toLowerCase().includes(kw)||(g.serials||[]).join(' ').toLowerCase().includes(kw)),
|
||||
sortValue:(g,k)=>{ if(k==='count')return (g.serials||[]).length; return g[k]||''; },
|
||||
render:renderGroupRow,
|
||||
tbody:'tb-groups',pager:'pager-groups',pageSize:10,
|
||||
empty:'<tr><td colspan="5" class="empty">暂无分组,点击"新建分组"创建</td></tr>'
|
||||
@@ -2315,6 +2348,7 @@ async function loadUsers(){
|
||||
setListPager('users',{
|
||||
data:()=>users,
|
||||
filter:(u,kw)=>(u.username||'').toLowerCase().includes(kw),
|
||||
sortValue:(u,k)=>{ if(k==='is_admin')return u.is_admin?1:0; return u[k]||''; },
|
||||
render:renderUserRow,
|
||||
tbody:'tb-users',pager:'pager-users',pageSize:10,
|
||||
empty:'<tr><td colspan="5" class="empty">暂无用户</td></tr>'
|
||||
@@ -2515,6 +2549,13 @@ async function loadTailscaleDevices(){
|
||||
setListPager('tailscale',{
|
||||
data:()=>_tsDevices,
|
||||
filter:(d,kw)=>((d.hostname||'').toLowerCase().includes(kw)||(d.name||'').toLowerCase().includes(kw)||(d.addresses||[]).join(',').includes(kw)),
|
||||
sortValue:(d,k)=>{
|
||||
if(k==='ip')return (d.addresses||[])[0]||'';
|
||||
if(k==='online'){ if(d.online===true)return 0; if(d.online===false)return 2; return 1; } // null=最近12h在线
|
||||
if(k==='authorized')return d.authorized?1:0;
|
||||
if(k==='key_expiry')return d.key_expiry_disabled?1:0;
|
||||
return d[k]||'';
|
||||
},
|
||||
render:renderTsRow,
|
||||
tbody:'tb-tailscale',pager:'pager-tailscale',pageSize:10,
|
||||
empty:'<tr><td colspan="8" class="empty">tailnet 暂无设备</td></tr>'
|
||||
@@ -2637,6 +2678,7 @@ async function loadApks(){
|
||||
document.getElementById('apk-hint').textContent='共 '+apks.length+' 个应用';
|
||||
setListPager('apks',{
|
||||
data:()=>apks,
|
||||
sortValue:(a,k)=>{ if(k==='size')return a.size||0; if(k==='upload_time')return a.upload_time||''; if(k==='package_name')return a.package_name||''; if(k==='version_name')return a.version_name||''; return a.display_name||''; },
|
||||
filter:(a,kw)=>((a.display_name||'').toLowerCase().includes(kw)||(a.package_name||'').toLowerCase().includes(kw)),
|
||||
render:renderApkRow,
|
||||
tbody:'tb-apks',pager:'pager-apks',pageSize:10,
|
||||
@@ -2695,6 +2737,7 @@ async function loadDeviceApps(){
|
||||
setListPager('devapps',{
|
||||
data:()=>_devAppsData||[],
|
||||
filter:(a,kw)=>((a.label||a.package||'').toLowerCase().includes(kw)||(a.package||'').toLowerCase().includes(kw)||(a.path||'').toLowerCase().includes(kw)),
|
||||
sortValue:(a,k)=>{ if(k==='version_code')return parseInt(a.version_code)||0; if(k==='version_name')return a.version_name||''; if(k==='label')return (a.label||a.package||''); return a[k]||''; },
|
||||
render:renderDevAppRow,
|
||||
tbody:'tb-device-apps',pager:'pager-devapps',pageSize:20,
|
||||
empty:'<tr><td colspan="4" class="empty">无应用</td></tr>'
|
||||
|
||||
@@ -321,15 +321,15 @@ select.form-control{cursor:pointer}
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th class="col-cb"><input type="checkbox" id="cb-all" onclick="toggleAllDevices(this.checked)" title="全选/取消全选"></th>
|
||||
<th class="sortable" data-key="serial" onclick="sortBy('serial')">设备</th>
|
||||
<th class="sortable" data-key="model" onclick="sortBy('model')">型号</th>
|
||||
<th class="sortable" data-key="stf_occupied" onclick="sortBy('stf_occupied')">STF</th>
|
||||
<th class="sortable" data-key="worker_status" onclick="sortBy('worker_status')">任务状态</th>
|
||||
<th class="sortable" data-key="foreground_app" onclick="sortBy('foreground_app')">前台App</th>
|
||||
<th class="sortable" data-key="progress" onclick="sortBy('progress')">进度</th>
|
||||
<th class="sortable" data-key="task_job" onclick="sortBy('task_job')">当前任务</th>
|
||||
<th class="sortable" data-key="current_action" onclick="sortBy('current_action')">当前动作</th>
|
||||
<th class="sortable" data-key="last_error" onclick="sortBy('last_error')">最近错误</th>
|
||||
<th data-sort="serial" onclick="_lpSort('devices','serial')">设备</th>
|
||||
<th data-sort="model" onclick="_lpSort('devices','model')">型号</th>
|
||||
<th data-sort="stf_occupied" onclick="_lpSort('devices','stf_occupied')">STF</th>
|
||||
<th data-sort="worker_status" onclick="_lpSort('devices','worker_status')">任务状态</th>
|
||||
<th data-sort="foreground_app" onclick="_lpSort('devices','foreground_app')">前台App</th>
|
||||
<th data-sort="progress" onclick="_lpSort('devices','progress')">进度</th>
|
||||
<th data-sort="task_job" onclick="_lpSort('devices','task_job')">当前任务</th>
|
||||
<th data-sort="current_action" onclick="_lpSort('devices','current_action')">当前动作</th>
|
||||
<th data-sort="last_error" onclick="_lpSort('devices','last_error')">最近错误</th>
|
||||
<th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-devices"></tbody>
|
||||
@@ -369,7 +369,7 @@ select.form-control{cursor:pointer}
|
||||
</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>
|
||||
<th data-sort="id" onclick="_lpSort('tasks','id')">ID</th><th data-sort="name" onclick="_lpSort('tasks','name')">任务名</th><th data-sort="task_type" onclick="_lpSort('tasks','task_type')">类型</th><th data-sort="enabled" onclick="_lpSort('tasks','enabled')">启用</th><th>目标</th><th>调度</th><th data-sort="next_run" onclick="_lpSort('tasks','next_run')">下次运行</th><th>重试</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-tasks"></tbody>
|
||||
</table>
|
||||
@@ -385,7 +385,7 @@ select.form-control{cursor:pointer}
|
||||
</div>
|
||||
<div class="help">把常用步骤打包成可复用的动作,拖入任务步骤编辑器使用;也可在任务编辑器里"☑选中步骤→打包"生成</div>
|
||||
<table class="table">
|
||||
<thead><tr><th>图标</th><th>动作名</th><th>步骤数</th><th>创建时间</th><th>操作</th></tr></thead>
|
||||
<thead><tr><th>图标</th><th data-sort="name" onclick="_lpSort('actions','name')">动作名</th><th data-sort="steps" onclick="_lpSort('actions','steps')">步骤数</th><th data-sort="created_at" onclick="_lpSort('actions','created_at')">创建时间</th><th>操作</th></tr></thead>
|
||||
<tbody id="tb-actions"></tbody>
|
||||
</table>
|
||||
<div class="list-pager" id="pager-actions"></div>
|
||||
@@ -403,7 +403,7 @@ select.form-control{cursor:pointer}
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>分组名</th><th>描述</th><th>设备数</th><th>设备列表</th><th>操作</th>
|
||||
<th data-sort="name" onclick="_lpSort('groups','name')">分组名</th><th>描述</th><th data-sort="count" onclick="_lpSort('groups','count')">设备数</th><th>设备列表</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-groups"></tbody>
|
||||
</table>
|
||||
@@ -445,7 +445,7 @@ select.form-control{cursor:pointer}
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>ID</th><th>用户名</th><th>管理员</th><th>权限</th><th>操作</th>
|
||||
<th data-sort="id" onclick="_lpSort('users','id')">ID</th><th data-sort="username" onclick="_lpSort('users','username')">用户名</th><th data-sort="is_admin" onclick="_lpSort('users','is_admin')">管理员</th><th>权限</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-users"></tbody>
|
||||
</table>
|
||||
@@ -561,7 +561,7 @@ select.form-control{cursor:pointer}
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>主机名</th><th>显示名</th><th>IP</th><th>系统</th><th>在线</th><th>授权</th><th>密钥过期</th><th>操作</th>
|
||||
<th data-sort="hostname" onclick="_lpSort('tailscale','hostname')">主机名</th><th data-sort="name" onclick="_lpSort('tailscale','name')">显示名</th><th data-sort="ip" onclick="_lpSort('tailscale','ip')">IP</th><th data-sort="os" onclick="_lpSort('tailscale','os')">系统</th><th data-sort="online" onclick="_lpSort('tailscale','online')">在线</th><th data-sort="authorized" onclick="_lpSort('tailscale','authorized')">授权</th><th data-sort="key_expiry" onclick="_lpSort('tailscale','key_expiry')">密钥过期</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-tailscale"><tr><td colspan="8" class="empty">点击"刷新设备"加载</td></tr></tbody>
|
||||
</table>
|
||||
@@ -582,7 +582,7 @@ select.form-control{cursor:pointer}
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>应用名</th><th>包名</th><th>版本</th><th>大小</th><th>上传时间</th><th>操作</th>
|
||||
<th data-sort="name" onclick="_lpSort('apks','name')">应用名</th><th data-sort="package_name" onclick="_lpSort('apks','package_name')">包名</th><th data-sort="version_name" onclick="_lpSort('apks','version_name')">版本</th><th data-sort="size" onclick="_lpSort('apks','size')">大小</th><th data-sort="upload_time" onclick="_lpSort('apks','upload_time')">上传时间</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-apks"></tbody>
|
||||
</table>
|
||||
@@ -595,14 +595,19 @@ select.form-control{cursor:pointer}
|
||||
<input type="text" id="appver-pkg" class="form-control" style="width:340px;font-family:monospace"
|
||||
placeholder="包名,如 com.ss.android.ugc.aweme" onkeydown="if(event.key==='Enter')queryAppVers()">
|
||||
<button class="btn btn-primary" onclick="queryAppVers()">查询版本</button>
|
||||
<input class="form-control list-search" id="search-appver" placeholder="搜索设备/版本...">
|
||||
<span id="appver-status" class="text-muted"></span>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>设备</th><th>状态</th><th>版本号</th><th>versionCode</th>
|
||||
<th data-sort="serial" onclick="_lpSort('appver','serial')">设备</th>
|
||||
<th data-sort="state" onclick="_lpSort('appver','state')">状态</th>
|
||||
<th data-sort="version" onclick="_lpSort('appver','version')">版本号</th>
|
||||
<th data-sort="code" onclick="_lpSort('appver','code')">versionCode</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-appver"><tr><td colspan="4" class="empty">输入包名后点击"查询版本"</td></tr></tbody>
|
||||
</table>
|
||||
<div class="list-pager" id="pager-appver"></div>
|
||||
<div class="help">查询所有设备(本地 adb 含 USB + STF 池)上该包名的安装情况与版本号,用于对比各设备 App 版本是否一致;未连接的 STF 设备会自动连接(绝不断开,不影响任务)。</div>
|
||||
</div>
|
||||
|
||||
@@ -619,7 +624,7 @@ select.form-control{cursor:pointer}
|
||||
<div style="max-height:420px;overflow:auto;border:1px solid var(--border);border-radius:8px">
|
||||
<table class="table" style="margin:0">
|
||||
<thead><tr>
|
||||
<th>应用</th><th>版本号</th><th>versionCode</th><th>APK 路径</th>
|
||||
<th data-sort="label" onclick="_lpSort('devapps','label')">应用</th><th data-sort="version_name" onclick="_lpSort('devapps','version_name')">版本号</th><th data-sort="version_code" onclick="_lpSort('devapps','version_code')">versionCode</th><th>APK 路径</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-device-apps">
|
||||
<tr><td colspan="4" class="empty">选择设备后点击"查看应用"</td></tr>
|
||||
@@ -636,14 +641,19 @@ select.form-control{cursor:pointer}
|
||||
placeholder="设备 IP,如 100.100.10.20" onkeydown="if(event.key==='Enter')addStfDev()">
|
||||
<button class="btn btn-primary" onclick="addStfDev()">添加设备并连接</button>
|
||||
<button class="btn" onclick="loadStfDevMgmt()">刷新</button>
|
||||
<input class="form-control list-search" id="search-stfdev" placeholder="搜索 IP...">
|
||||
<span id="stfdev-status" class="text-muted"></span>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>设备</th><th>脚本配置</th><th>连接状态(220 adb)</th><th>操作</th>
|
||||
<th data-sort="ip" onclick="_lpSort('stfdev','ip')">设备</th>
|
||||
<th data-sort="cfg" onclick="_lpSort('stfdev','cfg')">脚本配置</th>
|
||||
<th data-sort="state" onclick="_lpSort('stfdev','state')">连接状态(220 adb)</th>
|
||||
<th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tb-stfdev"><tr><td colspan="4" class="empty">点击"刷新"加载</td></tr></tbody>
|
||||
</table>
|
||||
<div class="list-pager" id="pager-stfdev"></div>
|
||||
<div class="help">
|
||||
管理 220 上 OpenSTF 设备池:添加 = 写入 connect_devices.sh 的 DEVICES 列表并立即 adb connect;
|
||||
移除 = 从脚本删除并 adb disconnect(设备从 STF 池下线,任务将不再分配到它)。
|
||||
|
||||
Reference in New Issue
Block a user