diff --git a/sdk/app/services/connection_priority.py b/sdk/app/services/connection_priority.py
index 4b5625000d..f87eb4d0c0 100644
--- a/sdk/app/services/connection_priority.py
+++ b/sdk/app/services/connection_priority.py
@@ -190,8 +190,11 @@ class ConnectionPriorityManager:
@staticmethod
def _check_hook(device_id: str, hook_service) -> bool:
try:
- modules = hook_service.get_device_modules(device_id) if hasattr(hook_service, "get_device_modules") else []
- return bool(modules)
+ if not hasattr(hook_service, "_read_json"):
+ return False
+ state = hook_service._read_json(hook_service.device_state_file, {})
+ item = state.get(device_id, {})
+ return bool(item.get("supports_hook", False))
except Exception:
return False
diff --git a/sdk/app/static/hub.html b/sdk/app/static/hub.html
index 21a2f2f1f3..dd1a6d240d 100644
--- a/sdk/app/static/hub.html
+++ b/sdk/app/static/hub.html
@@ -501,25 +501,37 @@ function renderOverview(){
设备快照
-
参考奥创后台和存客宝工作手机页,把设备状态、当前 App、网络和心跳统一收口。
+
实时展示设备状态、连接模式和心跳。点击设备行可查看详情。
+
+
+
- | 设备 | 状态 | 项目 | 当前 APP | 网络 | 心跳 |
+ | 设备 | 状态 | 连接模式 | 项目 | 当前 APP | 心跳 |
- ${devices.length ? devices.slice(0, 8).map(d => `
-
- | ${esc(d.model || d.device_id || '-')} |
- ${esc(d.status || '-')} |
+ ${devices.length ? devices.slice(0, 8).map(d => {
+ const best = d.best_mode;
+ const modes = (d.connection_modes || []).filter(m => m.available);
+ const statusClass = d.status === 'online' ? 'online' : (d.status === 'adb' ? 'adb' : '');
+ return `
+
+ | ${esc(d.model || d.device_id || '-')} |
+ ${esc(d.status || '-')} |
+
+
+ ${best ? `${esc(best.name)}` : ''}
+ ${modes.filter(m => !best || m.id !== best.id).slice(0,2).map(m => `${esc(m.id)}`).join('')}
+
+ |
${esc(d.project_id || '-')} |
${esc(d.current_app || '-')} |
- ${esc(d.network_type || d.quick_status?.network_type || '-')} |
- ${typeof d.heartbeat_age_seconds === 'number' ? d.heartbeat_age_seconds + 's' : '-'} |
-
- `).join('') : `| 暂无设备数据 |
`}
+ ${typeof d.heartbeat_age_seconds === 'number' ? d.heartbeat_age_seconds + 's' : (d.last_heartbeat ? '活跃' : '-')} |
+ `;
+ }).join('') : `| 暂无设备数据 |
`}
@@ -636,16 +648,24 @@ function renderDevicePanel(){
设备列表
- ${devices.length ? devices.map(device => `
+ ${devices.length ? devices.map(device => {
+ const best = device.best_mode;
+ const avail = (device.connection_modes || []).filter(m => m.available);
+ return `
📱
${esc(device.model || device.device_id)}
-
${esc(device.device_id)} · ${esc(device.status || '-')} · ${esc(device.project_id || '未绑定项目')}
+
${esc(device.device_id?.substring(0,12))}... · ${esc(device.status || '-')} · ${esc(device.project_id || '未绑定')}
+
+ ${best ? `${esc(best.name)}` : ''}
+ ${avail.filter(m => !best || m.id !== best.id).slice(0,3).map(m => `${esc(m.id)}`).join('')}
+ ${avail.length === 0 ? '离线' : ''}
+
-
- `).join('') : `
暂无设备,请先接入 Agent 或连接 ADB 设备
`}
+
`;
+ }).join('') : `
暂无设备,请先接入 Agent 或连接 ADB 设备
`}
@@ -717,16 +737,30 @@ function renderDevicePanel(){
设备详情
${current ? `
- | 设备 ID | ${esc(current.device_id)} |
+ | 设备 ID | ${esc(current.device_id)} |
| 型号 | ${esc(current.model || '-')} |
| 品牌 | ${esc(current.brand || '-')} |
- | Android | ${esc(current.android_version || '-')} |
- | 状态 | ${esc(current.status || '-')} |
+ | Android | ${esc(current.android_version || current.sdk_version || '-')} |
+ | 状态 | ${esc(current.status || '-')} |
+ | 当前模式 | ${current.best_mode ? `${esc(current.best_mode.name)}` : '-'} |
| 当前 APP | ${esc(current.current_app || current.quick_status?.current_app || '-')} |
| 网络 | ${esc(current.network_type || current.quick_status?.network_type || '-')} |
| 能力 | ${esc((current.capabilities || []).join(', ') || '-')} |
- | 已装 APP | ${esc((current.installed_apps || current.apps || []).join(', ') || '-')} |
+ ${(current.connection_modes || []).length ? `
+ 连接模式 (${(current.connection_modes||[]).filter(m=>m.available).length}/5 可用)
+
+ ${(current.connection_modes || []).map(m => {
+ const icons = {hook:'🪝', agent:'🔌', adb:'📱', ai:'🤖', scrcpy:'🖥️'};
+ const cls = m.available ? (current.best_mode?.id === m.id ? 'best-active' : 'active') : '';
+ return `
+
${icons[m.id] || '⚡'}
+
${esc(m.name)}
+
${m.available ? '✓ 可用' : '✗ 不可用'}
+
P${m.priority} · ${esc(m.detail?.substring(0,20) || '')}
+
`;
+ }).join('')}
+
` : ''}
` : `未选中设备
`}
@@ -1191,6 +1225,30 @@ async function refreshDevices(){
if (state.currentDevice) await loadDeviceDetail(state.currentDevice);
}
+async function refreshAll(){
+ await Promise.all([refreshOverview(), refreshConnection(), refreshDocs()]);
+ log('全部数据已刷新', 'info');
+}
+
+async function refreshConnectionModes(){
+ try {
+ const res = await apiGet('/api/v3/connection/modes');
+ const modeData = res.data || [];
+ for (const item of modeData) {
+ state.devices = (state.devices || []).map(d => {
+ if (d.device_id === item.device_id) {
+ return {...d, connection_modes: item.modes, best_mode: item.best_mode};
+ }
+ return d;
+ });
+ }
+ log(`连接模式检测完成:${modeData.length} 台设备`, 'info');
+ renderCurrentView();
+ } catch(error) {
+ log(`连接模式检测失败:${error.message}`, 'error');
+ }
+}
+
function selectDevice(deviceId){
state.currentDevice = deviceId;
renderCurrentView();
@@ -1201,9 +1259,14 @@ function selectDevice(deviceId){
async function loadDeviceDetail(deviceId){
try{
- const res = await apiGet(`/api/v3/devices/${deviceId}`);
- const detail = res.data || {};
- state.devices = (state.devices || []).map(item => item.device_id === deviceId ? {...item, ...detail} : item);
+ const [detailRes, modesRes] = await Promise.all([
+ apiGet(`/api/v3/devices/${deviceId}`),
+ apiGet(`/api/v3/connection/modes/${deviceId}`).catch(() => null)
+ ]);
+ const detail = detailRes.data || {};
+ const modes = modesRes?.data || {};
+ const merged = {...detail, connection_modes: modes.modes || [], best_mode: modes.best_mode || null};
+ state.devices = (state.devices || []).map(item => item.device_id === deviceId ? {...item, ...merged} : item);
if (state.current === 'devices') renderCurrentView();
}catch(error){
log(`设备详情加载失败:${error.message}`, 'error');