fix: connection_priority、hub.html 更新

Made-with: Cursor
This commit is contained in:
卡若
2026-03-16 10:31:33 +08:00
parent 2d5bd2200b
commit 35fdeb3bed
2 changed files with 89 additions and 23 deletions

View File

@@ -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

View File

@@ -501,25 +501,37 @@ function renderOverview(){
<div class="panel-head">
<div>
<h2>设备快照</h2>
<p class="sub">参考奥创后台和存客宝工作手机页,把设备状态、当前 App、网络和心跳统一收口。</p>
<p class="sub">实时展示设备状态、连接模式和心跳。点击设备行可查看详情。</p>
</div>
<div class="btns">
<button class="btn soft" onclick="refreshAll()">刷新全部</button>
</div>
</div>
<div class="card">
<table class="list-table">
<thead>
<tr><th>设备</th><th>状态</th><th>项目</th><th>当前 APP</th><th>网络</th><th>心跳</th></tr>
<tr><th>设备</th><th>状态</th><th>连接模式</th><th>项目</th><th>当前 APP</th><th>心跳</th></tr>
</thead>
<tbody>
${devices.length ? devices.slice(0, 8).map(d => `
<tr>
<td>${esc(d.model || d.device_id || '-')}</td>
<td>${esc(d.status || '-')}</td>
${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 `
<tr onclick="navigate('devices');selectDevice('${d.device_id}')" style="cursor:pointer">
<td><strong>${esc(d.model || d.device_id || '-')}</strong></td>
<td><span class="status-dot ${statusClass}" style="display:inline-block;vertical-align:middle;margin-right:4px"></span>${esc(d.status || '-')}</td>
<td>
<div class="mode-badges">
${best ? `<span class="mode-badge best"><span class="badge-dot"></span>${esc(best.name)}</span>` : ''}
${modes.filter(m => !best || m.id !== best.id).slice(0,2).map(m => `<span class="mode-badge available"><span class="badge-dot"></span>${esc(m.id)}</span>`).join('')}
</div>
</td>
<td>${esc(d.project_id || '-')}</td>
<td>${esc(d.current_app || '-')}</td>
<td>${esc(d.network_type || d.quick_status?.network_type || '-')}</td>
<td>${typeof d.heartbeat_age_seconds === 'number' ? d.heartbeat_age_seconds + 's' : '-'}</td>
</tr>
`).join('') : `<tr><td colspan="6" class="empty">暂无设备数据</td></tr>`}
<td>${typeof d.heartbeat_age_seconds === 'number' ? d.heartbeat_age_seconds + 's' : (d.last_heartbeat ? '活跃' : '-')}</td>
</tr>`;
}).join('') : `<tr><td colspan="6" class="empty">暂无设备数据</td></tr>`}
</tbody>
</table>
</div>
@@ -636,16 +648,24 @@ function renderDevicePanel(){
<div class="card">
<h3 style="margin-bottom:12px">设备列表</h3>
<div class="device-list">
${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 `
<div class="device-item ${device.device_id===state.currentDevice?'active':''}" onclick="selectDevice('${device.device_id}')">
<div class="device-badge">📱</div>
<div style="flex:1">
<div class="device-name">${esc(device.model || device.device_id)}</div>
<div class="device-meta">${esc(device.device_id)} · ${esc(device.status || '-')} · ${esc(device.project_id || '未绑定项目')}</div>
<div class="device-meta">${esc(device.device_id?.substring(0,12))}... · ${esc(device.status || '-')} · ${esc(device.project_id || '未绑定')}</div>
<div class="mode-badges">
${best ? `<span class="mode-badge best"><span class="badge-dot"></span>${esc(best.name)}</span>` : ''}
${avail.filter(m => !best || m.id !== best.id).slice(0,3).map(m => `<span class="mode-badge available"><span class="badge-dot"></span>${esc(m.id)}</span>`).join('')}
${avail.length === 0 ? '<span class="mode-badge unavailable">离线</span>' : ''}
</div>
</div>
<div class="status-dot ${esc(device.status || '')}"></div>
</div>
`).join('') : `<div class="empty">暂无设备,请先接入 Agent 或连接 ADB 设备</div>`}
</div>`;
}).join('') : `<div class="empty">暂无设备请先接入 Agent 或连接 ADB 设备</div>`}
</div>
</div>
@@ -717,16 +737,30 @@ function renderDevicePanel(){
<h3 style="margin-bottom:12px">设备详情</h3>
${current ? `
<table class="detail-table">
<tr><td>设备 ID</td><td>${esc(current.device_id)}</td></tr>
<tr><td>设备 ID</td><td style="font-family:monospace;font-size:11px;word-break:break-all">${esc(current.device_id)}</td></tr>
<tr><td>型号</td><td>${esc(current.model || '-')}</td></tr>
<tr><td>品牌</td><td>${esc(current.brand || '-')}</td></tr>
<tr><td>Android</td><td>${esc(current.android_version || '-')}</td></tr>
<tr><td>状态</td><td>${esc(current.status || '-')}</td></tr>
<tr><td>Android</td><td>${esc(current.android_version || current.sdk_version || '-')}</td></tr>
<tr><td>状态</td><td><span class="status-dot ${current.status || ''}" style="display:inline-block;vertical-align:middle;margin-right:4px"></span>${esc(current.status || '-')}</td></tr>
<tr><td>当前模式</td><td>${current.best_mode ? `<span class="mode-badge best" style="font-size:11px"><span class="badge-dot"></span>${esc(current.best_mode.name)}</span>` : '-'}</td></tr>
<tr><td>当前 APP</td><td>${esc(current.current_app || current.quick_status?.current_app || '-')}</td></tr>
<tr><td>网络</td><td>${esc(current.network_type || current.quick_status?.network_type || '-')}</td></tr>
<tr><td>能力</td><td>${esc((current.capabilities || []).join(', ') || '-')}</td></tr>
<tr><td>已装 APP</td><td>${esc((current.installed_apps || current.apps || []).join(', ') || '-')}</td></tr>
</table>
${(current.connection_modes || []).length ? `
<h3 style="margin:14px 0 8px">连接模式 (${(current.connection_modes||[]).filter(m=>m.available).length}/5 可用)</h3>
<div class="mode-detail-grid">
${(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 `<div class="mode-detail-card ${cls}">
<div class="mode-icon">${icons[m.id] || '⚡'}</div>
<div class="mode-name">${esc(m.name)}</div>
<div class="mode-status" style="color:${m.available ? 'var(--green)' : 'var(--muted)'}">${m.available ? '✓ 可用' : '✗ 不可用'}</div>
<div class="mode-prio">P${m.priority} · ${esc(m.detail?.substring(0,20) || '')}</div>
</div>`;
}).join('')}
</div>` : ''}
` : `<div class="empty">未选中设备</div>`}
</div>
</div>
@@ -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');