feat: 设备自动发现——定时扫描局域网+Tailscale 网段开放 adb 5555 的设备进入待连接池,用户确认后才加入正式设备池(不自动连接);socket 并发探测+adb 短超时验证(state=device 过滤 unauthorized),配置存 app_meta 可前端调整;配套:adb_connect_light 验证短超时、DISABLE_SCHEDULER 回归开关

This commit is contained in:
2026-08-30 10:57:07 +08:00
parent 1e39f5490a
commit 4efe17c1c9
11 changed files with 591 additions and 10 deletions
+81
View File
@@ -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);