@@ -671,13 +741,68 @@ function showTab(name){
if(_logTimer){clearInterval(_logTimer);_logTimer=null;}
// 按需加载数据
if(name==='monitor'){loadMonitor();_monitorTimer=setInterval(loadMonitor,5000);}
- if(name==='tasks')loadTasks();
+ if(name==='tasks'){loadTasks();loadCustomActions();}
if(name==='apps')loadApks();
if(name==='groups')loadGroups();
if(name==='logs'){loadLogs();if(document.getElementById('log-auto').checked)_logTimer=setInterval(loadLogs,3000);}
if(name==='users')loadUsers();
}
+// ================== 通用列表:搜索 + 分页 ==================
+// 用法:
+// 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');// 搜索框绑定
+var _LIST_PAGERS={};
+
+function _lp(key){ return _LIST_PAGERS[key] || (_LIST_PAGERS[key]={page:1,kw:'',cfg:null}); }
+
+function setListPager(key,cfg){
+ // 只更新配置,不重置 page/kw —— 监控列表 5 秒自动刷新时保持用户当前页
+ var st=_lp(key); st.cfg=cfg;
+}
+function initListSearch(key,inputId){
+ var input=document.getElementById(inputId);
+ if(!input||input.dataset.bound)return;
+ input.dataset.bound='1';
+ input.addEventListener('input',function(){
+ var st=_lp(key); st.kw=this.value.trim(); st.page=1; applyListPager(key);
+ });
+}
+function applyListPager(key){
+ var st=_lp(key);
+ if(!st.cfg)return;
+ var cfg=st.cfg;
+ var all=(cfg.data&&cfg.data())||[];
+ var kw=st.kw.toLowerCase();
+ var filtered=kw?all.filter(function(x){return cfg.filter(x,kw);}):all;
+ var pageSize=cfg.pageSize||10;
+ var pages=Math.max(1,Math.ceil(filtered.length/pageSize));
+ if(st.page>pages)st.page=pages;
+ if(st.page<1)st.page=1;
+ 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||'
| 无数据 |
');
+ var pg=document.getElementById(cfg.pager);
+ if(pg)pg.innerHTML=_lpPagerHtml(key,pages,st.page,filtered.length,pageSize);
+}
+function _lpPagerHtml(key,pages,cur,total,pageSize){
+ if(pages<=1)return total>0?'':'';
+ var h='';
+ return h;
+}
+function _lpGo(key,p){
+ var st=_lp(key); st.page=p; applyListPager(key);
+}
+
// ================== Tab 1: 监控 ==================
let _statusData=null;
let _jobsData=null;
@@ -732,7 +857,8 @@ async function loadMonitor(){
}catch(e){console.error(e);}
}
-// 渲染设备表格(带排序,复选状态保持)
+// 渲染设备表格(带排序+搜索+分页,复选状态保持)
+let _sortedDevices=[];
function renderDevices(){
if(!_statusData||!_statusData.devices){return;}
let devices=_statusData.devices.slice();
@@ -748,33 +874,44 @@ function renderDevices(){
return (va-vb)*dir;
});
}
- const rows=devices.map(dev=>{
- const stf=dev.stf_occupied?'
占用':'
空闲';
- const offline=dev.present?'':'
离线';
- const st=STATUS_MAP[dev.worker_status]||{t:dev.worker_status,c:'default'};
- const wb='
'+st.t+'';
- const fgApp=dev.foreground_app;
- const fg=fgApp?(
- fgApp==='(他人占用)'?'
他人占用':
- fgApp==='(不可达)'?'
不可达':
- '
'+esc(APP_NAMES[fgApp]||fgApp)+''
- ):'
-';
- const prog=fmtProgress(dev.progress, dev.worker_status);
- const tname=dev.task_job?'
'+esc(dev.task_job)+'':'
-';
- const retry=dev.attempt>0?'
'+dev.attempt+'次':'';
- const stopBtn=(dev.worker_status==='running'||dev.worker_status==='connecting')?'
':'';
- // 截图按钮(任何状态都可看,离线时会报错)
- const shotBtn='
';
- const btns='
'+shotBtn+stopBtn+'
';
- const devName=dev.device_name||dev.model||'';
- const devLabel=devName?'
'+esc(dev.serial)+''+esc(devName)+'':'
'+esc(dev.serial)+'';
- // 复选框列
- const checked=_selectedSerials.has(dev.serial)?'checked':'';
- const rowCls=_selectedSerials.has(dev.serial)?'row-checked':'';
- const cb='
';
- return '
| '+cb+' | '+devLabel+' | '+esc(dev.model)+' | '+stf+offline+' | '+wb+' | '+fg+' | '+prog+' | '+tname+retry+' | '+esc(dev.current_action||'-')+' | '+esc(dev.last_error||'-')+' | '+btns+' |
';
+ _sortedDevices=devices;
+ setListPager('devices',{
+ data:()=>_sortedDevices,
+ 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)),
+ render:renderDeviceRow,
+ tbody:'tb-devices',pager:'pager-devices',pageSize:10,
+ empty:'
| 暂无设备 |
'
});
- document.getElementById('tb-devices').innerHTML=rows.length?rows.join(''):'
| 暂无设备 |
';
+ applyListPager('devices');
+ initListSearch('devices','search-devices');
+}
+function renderDeviceRow(dev){
+ const stf=dev.stf_occupied?'
占用':'
空闲';
+ const offline=dev.present?'':'
离线';
+ const st=STATUS_MAP[dev.worker_status]||{t:dev.worker_status,c:'default'};
+ const wb='
'+st.t+'';
+ const fgApp=dev.foreground_app;
+ const fg=fgApp?(
+ fgApp==='(他人占用)'?'
他人占用':
+ fgApp==='(不可达)'?'
不可达':
+ '
'+esc(APP_NAMES[fgApp]||fgApp)+''
+ ):'
-';
+ const prog=fmtProgress(dev.progress, dev.worker_status);
+ const tname=dev.task_job?'
'+esc(dev.task_job)+'':'
-';
+ const retry=dev.attempt>0?'
'+dev.attempt+'次':'';
+ // 预计结束时间(设置了运行时长上限时显示)
+ const endInfo=(dev.worker_status==='running'&&dev.end_time)?'
预计结束 '+new Date(dev.end_time*1000).toLocaleTimeString()+'
':'';
+ const stopBtn=(dev.worker_status==='running'||dev.worker_status==='connecting')?'
':'';
+ // 截图按钮(任何状态都可看,离线时会报错)
+ const shotBtn='
';
+ const btns='
'+shotBtn+stopBtn+'
';
+ const devName=dev.device_name||dev.model||'';
+ const devLabel=devName?'
'+esc(dev.serial)+''+esc(devName)+'':'
'+esc(dev.serial)+'';
+ // 复选框列
+ const checked=_selectedSerials.has(dev.serial)?'checked':'';
+ const rowCls=_selectedSerials.has(dev.serial)?'row-checked':'';
+ const cb='
';
+ return '
| '+cb+' | '+devLabel+' | '+esc(dev.model)+' | '+stf+offline+' | '+wb+' | '+fg+' | '+prog+' | '+tname+retry+endInfo+' | '+esc(dev.current_action||'-')+' | '+esc(dev.last_error||'-')+' | '+btns+' |
';
}
async function loadJobsForMonitor(){
@@ -899,29 +1036,38 @@ async function loadTasks(){
function renderTaskTable(){
const jobs=_jobsData||[];
- document.getElementById('tb-tasks').innerHTML=jobs.length?jobs.map(j=>{
- const tgt=TARGET_MAP[j.target.mode]||j.target.mode;
- const tgtDetail=j.target.mode==='group'?(j.target.group_name||''):(j.target.mode==='serial'?(j.target.serial||''):'');
- const sched=j.schedule.mode==='cron_stop'?('启停 '+esc(j.schedule.cron||'')+'→'+esc(j.schedule.stop_cron||'')):
- j.schedule.mode==='cron'?('定时 '+esc(j.schedule.cron||'')):'手动';
- const retry=j.retry.max_attempts>1?(j.retry.max_attempts+'次/'+j.retry.delay+'s'):'不重试';
- const en=j.enabled?'
启用':'
停用';
- return '
'+
- ''+esc(j.id)+' | '+
- ''+esc(j.name)+' | '+
- ''+esc(j.task_type)+' | '+
- ''+en+' | '+
- ''+esc(tgt)+(tgtDetail?'('+esc(tgtDetail)+')':'')+' | '+
- ''+sched+' | '+
- ''+retry+' | '+
- ''+
- ''+
- ''+
- ''+
- ''+
- ' | '+
- '
';
- }).join(''):'
| 暂无任务,点击"新建任务"创建 |
';
+ setListPager('tasks',{
+ data:()=>jobs,
+ filter:(j,kw)=>((j.name||'').toLowerCase().includes(kw)||(j.task_type||'').toLowerCase().includes(kw)||(j.id||'').toLowerCase().includes(kw)),
+ render:renderTaskRow,
+ tbody:'tb-tasks',pager:'pager-tasks',pageSize:10,
+ empty:'
| 暂无任务,点击"新建任务"创建 |
'
+ });
+ applyListPager('tasks');
+ initListSearch('tasks','search-tasks');
+}
+function renderTaskRow(j){
+ const tgt=TARGET_MAP[j.target.mode]||j.target.mode;
+ const tgtDetail=j.target.mode==='group'?(j.target.group_name||''):(j.target.mode==='serial'?(j.target.serial||''):'');
+ const sched=j.schedule.mode==='cron_stop'?('启停 '+esc(j.schedule.cron||'')+'→'+esc(j.schedule.stop_cron||'')):
+ j.schedule.mode==='cron'?('定时 '+esc(j.schedule.cron||'')):'手动';
+ const retry=j.retry.max_attempts>1?(j.retry.max_attempts+'次/'+j.retry.delay+'s'):'不重试';
+ const en=j.enabled?'
启用':'
停用';
+ return '
'+
+ ''+esc(j.id)+' | '+
+ ''+esc(j.name)+' | '+
+ ''+esc(j.task_type)+' | '+
+ ''+en+' | '+
+ ''+esc(tgt)+(tgtDetail?'('+esc(tgtDetail)+')':'')+' | '+
+ ''+sched+' | '+
+ ''+retry+' | '+
+ ''+
+ ''+
+ ''+
+ ''+
+ ''+
+ ' | '+
+ '
';
}
function openTaskModal(jobId){
@@ -982,8 +1128,8 @@ function openTaskModal(jobId){
'