feat: 设备自动发现——定时扫描局域网+Tailscale 网段开放 adb 5555 的设备进入待连接池,用户确认后才加入正式设备池(不自动连接);socket 并发探测+adb 短超时验证(state=device 过滤 unauthorized),配置存 app_meta 可前端调整;配套:adb_connect_light 验证短超时、DISABLE_SCHEDULER 回归开关
This commit is contained in:
+10
-1
@@ -94,6 +94,7 @@ function showTab(name){
|
||||
// 停止所有定时器
|
||||
if(_monitorTimer){clearInterval(_monitorTimer);_monitorTimer=null;}
|
||||
if(_logTimer){clearInterval(_logTimer);_logTimer=null;}
|
||||
if(_discoveryTimer){clearInterval(_discoveryTimer);_discoveryTimer=null;}
|
||||
// 按需加载数据
|
||||
if(name==='monitor'){loadMonitor();_monitorTimer=setInterval(loadMonitor,5000);}
|
||||
if(name==='tasks'){showSubTab('tasks',_activeSubs.tasks);loadTasks();loadCustomActions();}
|
||||
@@ -106,13 +107,21 @@ function showTab(name){
|
||||
// ================== 页内子分栏(任务/工具 通用) ==================
|
||||
// 每个带子分栏的 Tab 记住上次选中的子分栏,切走再切回来保持原位
|
||||
let _activeSubs = {tasks: 'plan', tools: 'clipboard'};
|
||||
let _discoveryTimer = null; // 设备自动发现 10s 轮询(仅 devpool 子分栏激活时)
|
||||
|
||||
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));
|
||||
if(name==='devpool' && typeof loadDevPool==='function') loadDevPool();
|
||||
if(name==='devpool' && typeof loadDevPool==='function'){
|
||||
loadDevPool();
|
||||
if(typeof loadDiscovery==='function'){
|
||||
loadDiscovery();
|
||||
if(_discoveryTimer){clearInterval(_discoveryTimer);_discoveryTimer=null;}
|
||||
_discoveryTimer=setInterval(loadDiscovery,10000); // 10s 轮询:扫完自动出新结果
|
||||
}
|
||||
} else if(_discoveryTimer){clearInterval(_discoveryTimer);_discoveryTimer=null;}
|
||||
}
|
||||
|
||||
// ================== 模态框 ==================
|
||||
|
||||
@@ -39,6 +39,87 @@ async function loadToolsDevices(force){
|
||||
status.textContent = '共 '+_clipDevices.length+' 台设备';
|
||||
}
|
||||
|
||||
// ================== 设备自动发现(扫描 → 待连接池 → 确认) ==================
|
||||
function loadDiscovery(){
|
||||
apiGet('/api/devices/discovery').then(r=>{
|
||||
if(!r||!r.ok)return;
|
||||
const status=document.getElementById('discovery-status');
|
||||
const enabled=document.getElementById('discovery-enabled');
|
||||
const interval=document.getElementById('discovery-interval');
|
||||
const subnets=document.getElementById('discovery-subnets');
|
||||
if(!enabled)return; // 面板未渲染(离开子分栏)
|
||||
enabled.checked=!!r.enabled;
|
||||
interval.value=r.interval||60;
|
||||
subnets.value=(r.subnets||[]).join(', ');
|
||||
// 状态行
|
||||
if(r.scanning){status.textContent='扫描中...';}
|
||||
else if(r.last_scan){
|
||||
let txt='上次扫描 '+esc(r.last_scan)+' · 开放 '+r.last_result.found+' · 可连 '
|
||||
+(r.last_result.verified||0)+' · 新增 '+(r.last_result.new||0);
|
||||
if(r.last_error)txt+=' · ⚠ '+esc(r.last_error);
|
||||
status.textContent=txt;
|
||||
}else{status.textContent='尚未扫描';}
|
||||
// 待连接表格
|
||||
const rows=(r.pending||[]);
|
||||
const tb=document.getElementById('tb-discovery');
|
||||
if(!tb)return;
|
||||
if(!rows.length){
|
||||
tb.innerHTML='<tr><td colspan="6" class="empty">待连接池为空(扫描到的设备会出现在这里)</td></tr>';
|
||||
}else{
|
||||
tb.innerHTML=rows.map(x=>{
|
||||
const src=x.source==='tailscale'
|
||||
?'<span class="label label-info">Tailscale</span>'
|
||||
:'<span class="label label-warning">局域网</span>';
|
||||
const onl=x.online
|
||||
?'<span class="label label-success">在线</span>'
|
||||
:'<span class="label label-warning">离线</span>';
|
||||
return '<tr><td style="font-family:monospace">'+esc(x.serial)+'</td>'
|
||||
+'<td>'+src+'</td><td>'+esc(x.first_seen)+'</td><td>'+esc(x.last_seen)+'</td><td>'+onl+'</td>'
|
||||
+'<td><button class="btn btn-primary btn-xs" onclick="confirmDiscoveryDev(''+esc(x.serial)+'')">确认添加</button> '
|
||||
+'<button class="btn btn-xs" onclick="ignoreDiscoveryDev(''+esc(x.serial)+'')">忽略</button></td></tr>';
|
||||
}).join('');
|
||||
}
|
||||
});
|
||||
}
|
||||
function toggleDiscovery(checked){
|
||||
apiPost('/api/devices/discovery/settings',{enabled:checked}).then(r=>{
|
||||
if(r&&r.ok)showToast('定时扫描已'+(checked?'开启':'关闭'),'success');
|
||||
else showToast('设置失败: '+((r&&r.error)||''),'error');
|
||||
});
|
||||
}
|
||||
function saveDiscoverySettings(){
|
||||
const subnets=(document.getElementById('discovery-subnets').value||'')
|
||||
.split(',').map(s=>s.trim()).filter(Boolean);
|
||||
const interval=parseInt(document.getElementById('discovery-interval').value)||60;
|
||||
apiPost('/api/devices/discovery/settings',{subnets,interval}).then(r=>{
|
||||
if(r&&r.ok)showToast('设置已保存','success');
|
||||
else showToast('保存失败: '+((r&&r.error)||''),'error');
|
||||
});
|
||||
}
|
||||
function runDiscoveryScan(){
|
||||
apiPost('/api/devices/discovery/scan',{}).then(r=>{
|
||||
if(r&&r.ok){showToast(r.msg||'扫描已启动','success');setTimeout(loadDiscovery,3000);}
|
||||
else showToast('扫描失败: '+((r&&r.error)||''),'error');
|
||||
});
|
||||
}
|
||||
function confirmDiscoveryDev(serial){
|
||||
const name=prompt('备注名(可选,留空直接添加)','');
|
||||
if(name===null)return;
|
||||
apiPost('/api/devices/discovery/confirm',{serial,name}).then(r=>{
|
||||
if(r&&r.ok){
|
||||
showToast('已加入设备池'+(r.is_new?'':'(已存在,已更新)'),'success');
|
||||
loadDevPool();loadDiscovery();
|
||||
}else showToast('确认失败: '+((r&&r.error)||''),'error');
|
||||
});
|
||||
}
|
||||
function ignoreDiscoveryDev(serial){
|
||||
if(!confirm('忽略 '+serial+'?下轮扫描可能再次发现'))return;
|
||||
apiPost('/api/devices/discovery/ignore',{serial}).then(r=>{
|
||||
if(r&&r.ok){showToast('已忽略','success');loadDiscovery();}
|
||||
else showToast('操作失败: '+((r&&r.error)||''),'error');
|
||||
});
|
||||
}
|
||||
|
||||
function toggleClipDevice(cb){
|
||||
if(cb.checked)_clipSelected.add(cb.value);
|
||||
else _clipSelected.delete(cb.value);
|
||||
|
||||
Reference in New Issue
Block a user